Compare commits
6 Commits
dockerbox_
...
master
Author | SHA1 | Date | |
---|---|---|---|
57d90ef8a1
|
|||
cea6cddf54
|
|||
b0ca4dc906
|
|||
a68ef1fffc
|
|||
36d4ca2e6d
|
|||
0b23966f6e
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,3 +1,13 @@
|
|||||||
.vagrant
|
.vagrant
|
||||||
|
.vscode
|
||||||
.playbook
|
.playbook
|
||||||
|
/*.yml
|
||||||
|
/*.yaml
|
||||||
|
!backup.yml
|
||||||
|
!moxie.yml
|
||||||
|
!docker.yml
|
||||||
|
!dockerbox.yml
|
||||||
|
!hypervisor.yml
|
||||||
|
!minecraft.yml
|
||||||
|
!unifi.yml
|
||||||
/environments/
|
/environments/
|
||||||
|
5
Vagrantfile
vendored
5
Vagrantfile
vendored
@@ -20,7 +20,7 @@ else
|
|||||||
end
|
end
|
||||||
|
|
||||||
Vagrant.configure("2") do |config|
|
Vagrant.configure("2") do |config|
|
||||||
config.vm.box = "debian/bullseye64"
|
config.vm.box = "debian/contrib-buster64"
|
||||||
config.vm.network "private_network", type: "dhcp"
|
config.vm.network "private_network", type: "dhcp"
|
||||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||||
config.vm.synced_folder "./scratch", "/vagrant/scratch"
|
config.vm.synced_folder "./scratch", "/vagrant/scratch"
|
||||||
@@ -30,9 +30,8 @@ Vagrant.configure("2") do |config|
|
|||||||
config.vm.define :moxie do |moxie| #
|
config.vm.define :moxie do |moxie| #
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Disable Machine Name Prefix
|
||||||
config.vm.provider :libvirt do |libvirt|
|
config.vm.provider :libvirt do |libvirt|
|
||||||
libvirt.cpus = 2
|
|
||||||
libvirt.memory = 4096
|
|
||||||
libvirt.default_prefix = ""
|
libvirt.default_prefix = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
[defaults]
|
[defaults]
|
||||||
inventory = ./environments/development
|
inventory = ./environments/development
|
||||||
interpreter_python = /usr/bin/python3
|
interpreter_python = /usr/bin/python3
|
||||||
|
|
||||||
[connection]
|
|
||||||
pipelining = true
|
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
- docker
|
- docker
|
||||||
- traefik
|
- traefik
|
||||||
- nextcloud
|
- nextcloud
|
||||||
|
- gitea
|
||||||
- jenkins
|
- jenkins
|
||||||
- prometheus
|
- prometheus
|
||||||
- nginx
|
- nginx
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
base_domain: vm.krislamo.org
|
|
||||||
|
|
||||||
# base
|
|
||||||
allow_reboot: false
|
|
||||||
manage_network: false
|
|
||||||
|
|
||||||
# proxy
|
|
||||||
proxy:
|
|
||||||
#production: true
|
|
||||||
dns_cloudflare:
|
|
||||||
opts: --test-cert
|
|
||||||
#email: realemail@example.com
|
|
||||||
#api_token: CLOUDFLARE_DNS01_API_TOKEN
|
|
||||||
wildcard_domains:
|
|
||||||
- "{{ base_domain }}"
|
|
||||||
servers:
|
|
||||||
- domain: "{{ bitwarden_domain }}"
|
|
||||||
proxy_pass: "http://127.0.0.1:8080"
|
|
||||||
- domain: "{{ gitea_domain }}"
|
|
||||||
proxy_pass: "http://127.0.0.1:3000"
|
|
||||||
|
|
||||||
# docker
|
|
||||||
docker_users:
|
|
||||||
- vagrant
|
|
||||||
|
|
||||||
# bitwarden
|
|
||||||
# Get Installation ID & Key at https://bitwarden.com/host/
|
|
||||||
bitwarden_domain: "vault.{{ base_domain }}"
|
|
||||||
bitwarden_dbpass: password
|
|
||||||
bitwarden_install_id: 4ea840a3-532e-4cb6-a472-abd900728b23
|
|
||||||
bitwarden_install_key: 1yB3Z2gRI0KnnH90C6p
|
|
||||||
#bitwarden_prodution: true
|
|
||||||
|
|
||||||
# gitea
|
|
||||||
gitea_domain: "git.{{ base_domain }}"
|
|
||||||
gitea_version: 1
|
|
||||||
gitea_dbpass: password
|
|
@@ -1,12 +0,0 @@
|
|||||||
- name: Install Proxy Server
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
vars_files:
|
|
||||||
- host_vars/proxy.yml
|
|
||||||
roles:
|
|
||||||
- base
|
|
||||||
- mariadb
|
|
||||||
- proxy
|
|
||||||
- docker
|
|
||||||
- gitea
|
|
||||||
- bitwarden
|
|
@@ -1,3 +1,17 @@
|
|||||||
|
# Copyright (C) 2020 Kris Lamoureux
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
- name: Install Docker Box Server
|
- name: Install Docker Box Server
|
||||||
hosts: dockerhosts
|
hosts: dockerhosts
|
||||||
become: true
|
become: true
|
||||||
@@ -6,6 +20,7 @@
|
|||||||
- docker
|
- docker
|
||||||
- traefik
|
- traefik
|
||||||
- nextcloud
|
- nextcloud
|
||||||
|
# - gitea
|
||||||
- jenkins
|
- jenkins
|
||||||
- prometheus
|
- prometheus
|
||||||
- nginx
|
- nginx
|
||||||
|
11
proxy.yml
11
proxy.yml
@@ -1,11 +0,0 @@
|
|||||||
- name: Install Proxy Server
|
|
||||||
hosts: proxyhosts
|
|
||||||
become: true
|
|
||||||
roles:
|
|
||||||
- base
|
|
||||||
- jenkins
|
|
||||||
- mariadb
|
|
||||||
- proxy
|
|
||||||
- docker
|
|
||||||
- gitea
|
|
||||||
- bitwarden
|
|
3
roles/.gitignore
vendored
3
roles/.gitignore
vendored
@@ -7,13 +7,10 @@
|
|||||||
!gitea*/
|
!gitea*/
|
||||||
!jenkins*/
|
!jenkins*/
|
||||||
!libvirt*/
|
!libvirt*/
|
||||||
!mariadb*/
|
|
||||||
!minecraft*/
|
!minecraft*/
|
||||||
!nextcloud*/
|
!nextcloud*/
|
||||||
!nginx*/
|
!nginx*/
|
||||||
!postgresql*/
|
|
||||||
!prometheus*/
|
!prometheus*/
|
||||||
!proxy*/
|
|
||||||
!rsnapshot*/
|
!rsnapshot*/
|
||||||
!traefik*/
|
!traefik*/
|
||||||
!unifi*/
|
!unifi*/
|
||||||
|
@@ -3,7 +3,6 @@ network_type: static
|
|||||||
allow_reboot: true
|
allow_reboot: true
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
- apache2-utils
|
|
||||||
- cryptsetup
|
- cryptsetup
|
||||||
- curl
|
- curl
|
||||||
- dnsutils
|
- dnsutils
|
||||||
|
1
roles/base/files/buster-backports.list
Normal file
1
roles/base/files/buster-backports.list
Normal file
@@ -0,0 +1 @@
|
|||||||
|
deb http://deb.debian.org/debian buster-backports main
|
@@ -1,5 +1,5 @@
|
|||||||
- name: Reboot host
|
- name: Reboot host
|
||||||
ansible.builtin.reboot:
|
reboot:
|
||||||
msg: "Reboot initiated by Ansible"
|
msg: "Reboot initiated by Ansible"
|
||||||
connect_timeout: 5
|
connect_timeout: 5
|
||||||
listen: reboot_host
|
listen: reboot_host
|
||||||
@@ -10,9 +10,3 @@
|
|||||||
name: wg-quick@wg0
|
name: wg-quick@wg0
|
||||||
state: restarted
|
state: restarted
|
||||||
listen: restart_wireguard
|
listen: restart_wireguard
|
||||||
|
|
||||||
- name: Restart Fail2ban
|
|
||||||
service:
|
|
||||||
name: fail2ban
|
|
||||||
state: restarted
|
|
||||||
listen: restart_fail2ban
|
|
||||||
|
@@ -13,11 +13,3 @@
|
|||||||
loop:
|
loop:
|
||||||
- aptitude
|
- aptitude
|
||||||
- python3-docker
|
- python3-docker
|
||||||
- python3-pymysql
|
|
||||||
- python3-psycopg2
|
|
||||||
|
|
||||||
- name: Create Ansible's temporary remote directory
|
|
||||||
file:
|
|
||||||
path: "~/.ansible/tmp"
|
|
||||||
state: directory
|
|
||||||
mode: 0700
|
|
||||||
|
@@ -1,46 +0,0 @@
|
|||||||
- name: Install the Uncomplicated Firewall
|
|
||||||
apt:
|
|
||||||
name: ufw
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Install Fail2ban
|
|
||||||
apt:
|
|
||||||
name: fail2ban
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Deny incoming traffic by default
|
|
||||||
ufw:
|
|
||||||
default: deny
|
|
||||||
direction: incoming
|
|
||||||
|
|
||||||
- name: Allow outgoing traffic by default
|
|
||||||
ufw:
|
|
||||||
default: allow
|
|
||||||
direction: outgoing
|
|
||||||
|
|
||||||
- name: Allow OpenSSH with rate limiting
|
|
||||||
ufw:
|
|
||||||
name: ssh
|
|
||||||
rule: limit
|
|
||||||
|
|
||||||
- name: Remove Fail2ban defaults-debian.conf
|
|
||||||
file:
|
|
||||||
path: /etc/fail2ban/jail.d/defaults-debian.conf
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Install OpenSSH's Fail2ban jail
|
|
||||||
template:
|
|
||||||
src: fail2ban-ssh.conf.j2
|
|
||||||
dest: /etc/fail2ban/jail.d/sshd.conf
|
|
||||||
notify: restart_fail2ban
|
|
||||||
|
|
||||||
- name: Install Fail2ban IP allow list
|
|
||||||
template:
|
|
||||||
src: fail2ban-allowlist.conf.j2
|
|
||||||
dest: /etc/fail2ban/jail.d/allowlist.conf
|
|
||||||
when: fail2ban_ignoreip is defined
|
|
||||||
notify: restart_fail2ban
|
|
||||||
|
|
||||||
- name: Enable firewall
|
|
||||||
ufw:
|
|
||||||
state: enabled
|
|
@@ -4,9 +4,6 @@
|
|||||||
- import_tasks: system.yml
|
- import_tasks: system.yml
|
||||||
tags: system
|
tags: system
|
||||||
|
|
||||||
- import_tasks: firewall.yml
|
|
||||||
tags: firewall
|
|
||||||
|
|
||||||
- import_tasks: network.yml
|
- import_tasks: network.yml
|
||||||
tags: network
|
tags: network
|
||||||
when: manage_network
|
when: manage_network
|
||||||
|
@@ -12,3 +12,8 @@
|
|||||||
dest: "/etc/network/interfaces.d/{{ item.name }}"
|
dest: "/etc/network/interfaces.d/{{ item.name }}"
|
||||||
loop: "{{ interfaces }}"
|
loop: "{{ interfaces }}"
|
||||||
notify: reboot_host
|
notify: reboot_host
|
||||||
|
|
||||||
|
- name: Install bridge utilities
|
||||||
|
apt:
|
||||||
|
name: bridge-utils
|
||||||
|
state: present
|
||||||
|
@@ -10,6 +10,12 @@
|
|||||||
dest: /root/.ssh/authorized_keys
|
dest: /root/.ssh/authorized_keys
|
||||||
when: authorized_keys is defined
|
when: authorized_keys is defined
|
||||||
|
|
||||||
|
- name: Install btrfs-tools
|
||||||
|
apt:
|
||||||
|
name: btrfs-tools
|
||||||
|
state: present
|
||||||
|
when: btrfs_support is defined and btrfs_support | bool == true
|
||||||
|
|
||||||
- name: Manage filesystem mounts
|
- name: Manage filesystem mounts
|
||||||
mount:
|
mount:
|
||||||
path: "{{ item.path }}"
|
path: "{{ item.path }}"
|
||||||
|
@@ -1,3 +1,25 @@
|
|||||||
|
# Copyright (C) 2021 Kris Lamoureux
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
- name: Add Debian Buster backports
|
||||||
|
copy:
|
||||||
|
src: buster-backports.list
|
||||||
|
dest: /etc/apt/sources.list.d/buster-backports.list
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
- name: Install WireGuard
|
- name: Install WireGuard
|
||||||
apt:
|
apt:
|
||||||
name: wireguard
|
name: wireguard
|
||||||
@@ -27,10 +49,3 @@
|
|||||||
name: wg-quick@wg0
|
name: wg-quick@wg0
|
||||||
state: started
|
state: started
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
- name: Add WireGuard firewall rule
|
|
||||||
ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ wireguard.listenport }}"
|
|
||||||
proto: tcp
|
|
||||||
when: wireguard.listenport is defined
|
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
ignoreip = {% for host in fail2ban_ignoreip %}{{ host }}{% if not loop.last %} {% endif %}{% endfor %}
|
|
@@ -1,3 +0,0 @@
|
|||||||
[sshd]
|
|
||||||
mode = aggressive
|
|
||||||
enabled = true
|
|
@@ -1,8 +1,4 @@
|
|||||||
bitwarden_name: bitwarden
|
bitwarden_name: bitwarden
|
||||||
bitwarden_root: "/var/lib/{{ bitwarden_name }}"
|
bitwarden_root: "/opt/{{ bitwarden_name }}"
|
||||||
bitwarden_logs_identity: "{{ bitwarden_root }}/bwdata/logs/identity/Identity"
|
|
||||||
bitwarden_logs_identity_date: "{{ ansible_date_time.year }}{{ ansible_date_time.month }}{{ ansible_date_time.day }}"
|
|
||||||
bitwarden_database: "{{ bitwarden_name }}"
|
|
||||||
bitwarden_realips: "172.16.0.0/12"
|
|
||||||
bitwarden_standalone: false
|
bitwarden_standalone: false
|
||||||
bitwarden_production: false
|
bitwarden_production: false
|
||||||
|
@@ -1,16 +1,7 @@
|
|||||||
- name: Stop Bitwarden for rebuild
|
|
||||||
service:
|
|
||||||
name: "{{ bitwarden_name }}"
|
|
||||||
state: stopped
|
|
||||||
listen: rebuild_bitwarden
|
|
||||||
|
|
||||||
- name: Rebuild Bitwarden
|
- name: Rebuild Bitwarden
|
||||||
shell: "{{ bitwarden_root }}/bitwarden.sh rebuild"
|
shell: "{{ bitwarden_root }}/bitwarden.sh rebuild"
|
||||||
listen: rebuild_bitwarden
|
listen: rebuild_bitwarden
|
||||||
|
|
||||||
- name: Start Bitwarden after rebuild
|
- name: Start Bitwarden
|
||||||
service:
|
shell: "{{ bitwarden_root }}/bitwarden.sh start"
|
||||||
name: "{{ bitwarden_name }}"
|
listen: start_bitwarden
|
||||||
state: started
|
|
||||||
enabled: true
|
|
||||||
listen: rebuild_bitwarden
|
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
- name: Download Bitwarden script
|
- name: Download Bitwarden script
|
||||||
get_url:
|
get_url:
|
||||||
url: "https://raw.githubusercontent.com/\
|
url: "https://raw.githubusercontent.com/\
|
||||||
bitwarden/self-host/master/bitwarden.sh"
|
bitwarden/server/master/scripts/bitwarden.sh"
|
||||||
dest: "{{ bitwarden_root }}"
|
dest: "{{ bitwarden_root }}"
|
||||||
mode: u+x
|
mode: u+x
|
||||||
|
|
||||||
@@ -25,29 +25,35 @@
|
|||||||
shell: "{{ bitwarden_root }}/bw_wrapper"
|
shell: "{{ bitwarden_root }}/bw_wrapper"
|
||||||
args:
|
args:
|
||||||
creates: "{{ bitwarden_root }}/bwdata/config.yml"
|
creates: "{{ bitwarden_root }}/bwdata/config.yml"
|
||||||
|
notify: start_bitwarden
|
||||||
|
|
||||||
- name: Install docker-compose override
|
- name: Install docker-compose override
|
||||||
template:
|
template:
|
||||||
src: compose.override.yml.j2
|
src: compose.override.yml.j2
|
||||||
dest: "{{ bitwarden_root }}/bwdata/docker/docker-compose.override.yml"
|
dest: "{{ bitwarden_root }}/bwdata/docker/docker-compose.override.yml"
|
||||||
when: traefik_version is defined
|
notify:
|
||||||
notify: rebuild_bitwarden
|
- rebuild_bitwarden
|
||||||
|
- start_bitwarden
|
||||||
|
|
||||||
- name: Disable bitwarden-nginx HTTP on 80
|
- name: Disable bitwarden-nginx HTTP on 80
|
||||||
replace:
|
replace:
|
||||||
path: "{{ bitwarden_root }}/bwdata/config.yml"
|
path: "{{ bitwarden_root }}/bwdata/config.yml"
|
||||||
regexp: "^http_port: 80$"
|
regexp: "^http_port: 80$"
|
||||||
replace: "http_port: 127.0.0.1:8080"
|
replace: "http_port: 8080"
|
||||||
when: not bitwarden_standalone
|
when: not bitwarden_standalone
|
||||||
notify: rebuild_bitwarden
|
notify:
|
||||||
|
- rebuild_bitwarden
|
||||||
|
- start_bitwarden
|
||||||
|
|
||||||
- name: Disable bitwarden-nginx HTTPS on 443
|
- name: Disable bitwarden-nginx HTTPS on 443
|
||||||
replace:
|
replace:
|
||||||
path: "{{ bitwarden_root }}/bwdata/config.yml"
|
path: "{{ bitwarden_root }}/bwdata/config.yml"
|
||||||
regexp: "^https_port: 443$"
|
regexp: "^https_port: 443$"
|
||||||
replace: "https_port: 127.0.0.1:8443"
|
replace: "https_port: 8443"
|
||||||
when: not bitwarden_standalone
|
when: not bitwarden_standalone
|
||||||
notify: rebuild_bitwarden
|
notify:
|
||||||
|
- rebuild_bitwarden
|
||||||
|
- start_bitwarden
|
||||||
|
|
||||||
- name: Disable Bitwarden managed Lets Encrypt
|
- name: Disable Bitwarden managed Lets Encrypt
|
||||||
replace:
|
replace:
|
||||||
@@ -55,7 +61,9 @@
|
|||||||
regexp: "^ssl_managed_lets_encrypt: true$"
|
regexp: "^ssl_managed_lets_encrypt: true$"
|
||||||
replace: "ssl_managed_lets_encrypt: false"
|
replace: "ssl_managed_lets_encrypt: false"
|
||||||
when: not bitwarden_standalone or not bitwarden_production
|
when: not bitwarden_standalone or not bitwarden_production
|
||||||
notify: rebuild_bitwarden
|
notify:
|
||||||
|
- rebuild_bitwarden
|
||||||
|
- start_bitwarden
|
||||||
|
|
||||||
- name: Disable Bitwarden managed SSL
|
- name: Disable Bitwarden managed SSL
|
||||||
replace:
|
replace:
|
||||||
@@ -63,42 +71,6 @@
|
|||||||
regexp: "^ssl: true$"
|
regexp: "^ssl: true$"
|
||||||
replace: "ssl: false"
|
replace: "ssl: false"
|
||||||
when: not bitwarden_standalone
|
when: not bitwarden_standalone
|
||||||
notify: rebuild_bitwarden
|
notify:
|
||||||
|
- rebuild_bitwarden
|
||||||
- name: Define reverse proxy servers
|
- start_bitwarden
|
||||||
lineinfile:
|
|
||||||
path: "{{ bitwarden_root }}/bwdata/config.yml"
|
|
||||||
line: "- {{ bitwarden_realips }}"
|
|
||||||
insertafter: "^real_ips"
|
|
||||||
notify: rebuild_bitwarden
|
|
||||||
|
|
||||||
- name: Install Bitwarden systemd service
|
|
||||||
template:
|
|
||||||
src: bitwarden.service.j2
|
|
||||||
dest: "/etc/systemd/system/{{ bitwarden_name }}.service"
|
|
||||||
register: bitwarden_systemd
|
|
||||||
notify: rebuild_bitwarden
|
|
||||||
|
|
||||||
- name: Create Bitwarden's initial logging directory
|
|
||||||
file:
|
|
||||||
path: "{{ bitwarden_logs_identity }}"
|
|
||||||
state: directory
|
|
||||||
register: bitwarden_logs
|
|
||||||
|
|
||||||
- name: Create Bitwarden's initial log file
|
|
||||||
file:
|
|
||||||
path: "{{ bitwarden_logs_identity }}/{{ bitwarden_logs_identity_date }}.txt"
|
|
||||||
state: touch
|
|
||||||
when: bitwarden_logs.changed
|
|
||||||
|
|
||||||
- name: Install Bitwarden's Fail2ban jail
|
|
||||||
template:
|
|
||||||
src: fail2ban-jail.conf.j2
|
|
||||||
dest: /etc/fail2ban/jail.d/bitwarden.conf
|
|
||||||
notify: restart_fail2ban
|
|
||||||
|
|
||||||
- name: Reload systemd manager configuration
|
|
||||||
systemd:
|
|
||||||
daemon_reload: true
|
|
||||||
when: bitwarden_systemd.changed
|
|
||||||
notify: rebuild_bitwarden
|
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Bitwarden Password Manager Server
|
|
||||||
PartOf=docker.service
|
|
||||||
After=docker.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
RemainAfterExit=true
|
|
||||||
ExecStart={{ bitwarden_root }}/bitwarden.sh start
|
|
||||||
ExecStop={{ bitwarden_root }}/bitwarden.sh stop
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
@@ -14,9 +14,6 @@ send "y\r"
|
|||||||
send "n\r"
|
send "n\r"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
expect "Enter the database name for your Bitwarden instance (ex. vault):"
|
|
||||||
send "{{ bitwarden_database }}\r"
|
|
||||||
|
|
||||||
expect "Enter your installation id (get at https://bitwarden.com/host):"
|
expect "Enter your installation id (get at https://bitwarden.com/host):"
|
||||||
send "{{ bitwarden_install_id }}\r"
|
send "{{ bitwarden_install_id }}\r"
|
||||||
|
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
networks:
|
networks:
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
# {{ ansible_managed }}
|
|
||||||
[bitwarden]
|
|
||||||
enabled = true
|
|
||||||
filter = bitwarden
|
|
||||||
logpath = {{ bitwarden_root }}/bwdata/logs/identity/Identity/*
|
|
||||||
maxretry = 10
|
|
||||||
findtime = 3600
|
|
||||||
bantime = 900
|
|
||||||
action = iptables-allports
|
|
@@ -1,3 +0,0 @@
|
|||||||
docker_compose_root: /var/lib/compose
|
|
||||||
docker_compose: /usr/bin/docker-compose
|
|
||||||
docker_compose_service: compose
|
|
1
roles/docker/docker-ce.list
Normal file
1
roles/docker/docker-ce.list
Normal file
@@ -0,0 +1 @@
|
|||||||
|
deb [arch=amd64] https://download.docker.com/linux/debian buster stable
|
38
roles/docker/install-compose.sh
Normal file
38
roles/docker/install-compose.sh
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Github username and repo name
|
||||||
|
user="docker"
|
||||||
|
repo="compose"
|
||||||
|
|
||||||
|
# Retrieve the latest version number
|
||||||
|
addr="https://github.com/$user/$repo/releases/latest"
|
||||||
|
page=$(curl -s $addr | grep -o releases/tag/*.*\")
|
||||||
|
version=$(echo $page | awk '{print substr($1, 14, length($1) - 14)}')
|
||||||
|
|
||||||
|
# Download prep
|
||||||
|
url="https://github.com/$user/$repo/releases/download/$version"
|
||||||
|
file="docker-compose-$(uname -s)-$(uname -m)"
|
||||||
|
|
||||||
|
# Download latest Docker Compose if that version hasn't been downloaded
|
||||||
|
if [ ! -f /tmp/docker_compose_$version ]; then
|
||||||
|
curl -L $url/$file -o /tmp/docker-compose_$version
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Is it already installed?
|
||||||
|
if installed=$(which docker-compose); then
|
||||||
|
|
||||||
|
new_chksum=$(sha256sum /tmp/docker-compose_$version)
|
||||||
|
old_chksum=$(sha256sum /usr/local/bin/docker-compose)
|
||||||
|
|
||||||
|
# If checksums are different, delete and install new version
|
||||||
|
if [ ! "$new_chksum" = "$old_chksum" ]; then
|
||||||
|
rm /usr/local/bin/docker-compose
|
||||||
|
mv /tmp/docker-compose_$version /usr/local/bin/docker-compose
|
||||||
|
chmod +x /usr/local/bin/docker-compose
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# It's not installed, so no need to remove
|
||||||
|
mv /tmp/docker-compose_$version /usr/local/bin/docker-compose
|
||||||
|
chmod +x /usr/local/bin/docker-compose
|
||||||
|
fi
|
@@ -1,24 +1,45 @@
|
|||||||
- name: Install Docker
|
# Copyright (C) 2019 Kris Lamoureux
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
- name: Remove old versions of Docker
|
||||||
apt:
|
apt:
|
||||||
name: ['docker.io', 'docker-compose']
|
name: ['docker', 'docker-engine', 'docker.io', 'containerd', 'runc']
|
||||||
state: present
|
state: absent
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
- name: Create docker-compose root
|
- name: Install HTTPS capability for apt
|
||||||
file:
|
apt:
|
||||||
path: "{{ docker_compose_root }}"
|
name: ['apt-transport-https', 'ca-certificates',
|
||||||
state: directory
|
'curl', 'gnupg2', 'software-properties-common']
|
||||||
|
state: present
|
||||||
|
|
||||||
- name: Install docker-compose systemd service
|
- name: Install Docker's signing key
|
||||||
|
apt_key:
|
||||||
|
url: https://download.docker.com/linux/debian/gpg
|
||||||
|
id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Install Docker's stable repository
|
||||||
template:
|
template:
|
||||||
src: docker-compose.service.j2
|
src: docker-ce.list
|
||||||
dest: "/etc/systemd/system/{{ docker_compose_service }}@.service"
|
dest: /etc/apt/sources.list.d/docker-ce.list
|
||||||
register: compose_systemd
|
|
||||||
|
|
||||||
- name: Reload systemd manager configuration
|
- name: Install Docker CE
|
||||||
systemd:
|
apt:
|
||||||
daemon_reload: true
|
name: ['docker-ce', 'docker-ce-cli', 'containerd.io']
|
||||||
when: compose_systemd.changed
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
- name: Add users to docker group
|
- name: Add users to docker group
|
||||||
user:
|
user:
|
||||||
@@ -28,6 +49,11 @@
|
|||||||
loop: "{{ docker_users }}"
|
loop: "{{ docker_users }}"
|
||||||
when: docker_users is defined
|
when: docker_users is defined
|
||||||
|
|
||||||
|
- name: Install docker-compose
|
||||||
|
script: install-compose.sh
|
||||||
|
args:
|
||||||
|
creates: /usr/local/bin/docker-compose
|
||||||
|
|
||||||
- name: Start Docker and enable on boot
|
- name: Start Docker and enable on boot
|
||||||
service:
|
service:
|
||||||
name: docker
|
name: docker
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=%i docker-compose service
|
|
||||||
PartOf=docker.service
|
|
||||||
After=docker.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
RemainAfterExit=true
|
|
||||||
WorkingDirectory={{ docker_compose_root }}/%i
|
|
||||||
ExecStart={{ docker_compose }} up -d --remove-orphans
|
|
||||||
ExecStop={{ docker_compose }} down
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
@@ -1,22 +1,11 @@
|
|||||||
# container settings
|
# container settings
|
||||||
gitea_name: gitea
|
gitea_name: gitea
|
||||||
gitea_sshport: "222"
|
gitea_dbname: "{{ gitea_name }}-db"
|
||||||
gitea_webport: "3000"
|
gitea_ports: "222:22"
|
||||||
gitea_ssh: "127.0.0.1:{{ gitea_sshport }}"
|
|
||||||
gitea_web: "127.0.0.1:{{ gitea_webport }}"
|
|
||||||
gitea_volume: "{{ gitea_name }}"
|
|
||||||
gitea_rooturl: "https://{{ gitea_domain }}"
|
|
||||||
gitea_signup: true
|
|
||||||
|
|
||||||
# database settings
|
# database settings
|
||||||
gitea_dbtype: mysql
|
gitea_dbuser: "{{ gitea_dbname }}"
|
||||||
gitea_dbhost: host.docker.internal
|
|
||||||
gitea_dbname: "{{ gitea_name }}"
|
|
||||||
gitea_dbuser: "{{ gitea_name }}"
|
|
||||||
|
|
||||||
# proxy settings
|
|
||||||
gitea_proxy_limit: "1"
|
|
||||||
gitea_trusted_proxies: "172.16.0.0/12"
|
|
||||||
|
|
||||||
# host
|
# host
|
||||||
gitea_root: "{{ docker_compose_root }}/{{ gitea_name }}"
|
gitea_root: "/opt/{{ gitea_name }}/data"
|
||||||
|
gitea_dbroot: "/opt/{{ gitea_name }}/database"
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
- name: Restart Gitea
|
|
||||||
service:
|
|
||||||
name: "{{ docker_compose_service }}@{{ gitea_name }}"
|
|
||||||
state: restarted
|
|
||||||
listen: restart_gitea
|
|
@@ -1,111 +1,53 @@
|
|||||||
- name: Create Gitea directory
|
- name: Create Gitea Network
|
||||||
file:
|
docker_network:
|
||||||
path: "{{ gitea_root }}"
|
name: "{{ gitea_name }}"
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: Create Gitea database
|
- name: Start Gitea's database container
|
||||||
mysql_db:
|
docker_container:
|
||||||
name: "{{ gitea_dbname }}"
|
name: "{{ gitea_dbname }}"
|
||||||
state: present
|
image: mariadb:{{ gitea_dbversion }}
|
||||||
login_unix_socket: /var/run/mysqld/mysqld.sock
|
|
||||||
|
|
||||||
- name: Create Gitea database user
|
|
||||||
mysql_user:
|
|
||||||
name: "{{ gitea_dbuser }}"
|
|
||||||
password: "{{ gitea_dbpass }}"
|
|
||||||
host: '%'
|
|
||||||
state: present
|
|
||||||
priv: "{{ gitea_dbname }}.*:ALL"
|
|
||||||
login_unix_socket: /var/run/mysqld/mysqld.sock
|
|
||||||
|
|
||||||
- name: Create git user
|
|
||||||
user:
|
|
||||||
name: git
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Git user uid
|
|
||||||
getent:
|
|
||||||
database: passwd
|
|
||||||
key: git
|
|
||||||
|
|
||||||
- name: Git user gid
|
|
||||||
getent:
|
|
||||||
database: group
|
|
||||||
key: git
|
|
||||||
|
|
||||||
- name: Create git's .ssh directory
|
|
||||||
file:
|
|
||||||
path: /home/git/.ssh
|
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: Generate git's SSH keys
|
|
||||||
openssh_keypair:
|
|
||||||
path: /home/git/.ssh/id_rsa
|
|
||||||
|
|
||||||
- name: Find git's public SSH key
|
|
||||||
slurp:
|
|
||||||
src: /home/git/.ssh/id_rsa.pub
|
|
||||||
register: git_rsapub
|
|
||||||
|
|
||||||
- name: Get stats on git's authorized_keys file
|
|
||||||
stat:
|
|
||||||
path: /home/git/.ssh/authorized_keys
|
|
||||||
register: git_authkeys
|
|
||||||
|
|
||||||
- name: Create git's authorized_keys file
|
|
||||||
file:
|
|
||||||
path: /home/git/.ssh/authorized_keys
|
|
||||||
state: touch
|
|
||||||
when: not git_authkeys.stat.exists
|
|
||||||
|
|
||||||
- name: Add git's public SSH key to authorized_keys
|
|
||||||
lineinfile:
|
|
||||||
path: /home/git/.ssh/authorized_keys
|
|
||||||
regex: "^ssh-rsa"
|
|
||||||
line: "{{ git_rsapub['content'] | b64decode }}"
|
|
||||||
|
|
||||||
- name: Create Gitea host script for SSH
|
|
||||||
template:
|
|
||||||
src: gitea.sh.j2
|
|
||||||
dest: /usr/local/bin/gitea
|
|
||||||
mode: 0755
|
|
||||||
|
|
||||||
- name: Install Gitea's docker-compose file
|
|
||||||
template:
|
|
||||||
src: docker-compose.yml.j2
|
|
||||||
dest: "{{ gitea_root }}/docker-compose.yml"
|
|
||||||
notify: restart_gitea
|
|
||||||
|
|
||||||
- name: Install Gitea's docker-compose variables
|
|
||||||
template:
|
|
||||||
src: compose-env.j2
|
|
||||||
dest: "{{ gitea_root }}/.env"
|
|
||||||
notify: restart_gitea
|
|
||||||
|
|
||||||
- name: Create Gitea's logging directory
|
|
||||||
file:
|
|
||||||
name: /var/log/gitea
|
|
||||||
state: directory
|
|
||||||
|
|
||||||
- name: Create Gitea's initial log file
|
|
||||||
file:
|
|
||||||
name: /var/log/gitea/gitea.log
|
|
||||||
state: touch
|
|
||||||
|
|
||||||
- name: Install Gitea's Fail2ban filter
|
|
||||||
template:
|
|
||||||
src: fail2ban-filter.conf.j2
|
|
||||||
dest: /etc/fail2ban/filter.d/gitea.conf
|
|
||||||
notify: restart_fail2ban
|
|
||||||
|
|
||||||
- name: Install Gitea's Fail2ban jail
|
|
||||||
template:
|
|
||||||
src: fail2ban-jail.conf.j2
|
|
||||||
dest: /etc/fail2ban/jail.d/gitea.conf
|
|
||||||
notify: restart_fail2ban
|
|
||||||
|
|
||||||
- name: Start and enable Gitea service
|
|
||||||
service:
|
|
||||||
name: "{{ docker_compose_service }}@{{ gitea_name }}"
|
|
||||||
state: started
|
state: started
|
||||||
enabled: true
|
restart_policy: always
|
||||||
|
volumes: "{{ gitea_dbroot }}:/var/lib/mysql"
|
||||||
|
networks_cli_compatible: true
|
||||||
|
networks:
|
||||||
|
- name: "{{ gitea_name }}"
|
||||||
|
env:
|
||||||
|
MYSQL_RANDOM_ROOT_PASSWORD: "true"
|
||||||
|
MYSQL_DATABASE: "{{ gitea_dbname }}"
|
||||||
|
MYSQL_USER: "{{ gitea_dbuser }}"
|
||||||
|
MYSQL_PASSWORD: "{{ gitea_dbpass }}"
|
||||||
|
|
||||||
|
- name: Start Gitea container
|
||||||
|
docker_container:
|
||||||
|
name: "{{ gitea_name }}"
|
||||||
|
image: gitea/gitea:{{ gitea_version }}
|
||||||
|
state: started
|
||||||
|
restart_policy: always
|
||||||
|
networks_cli_compatible: true
|
||||||
|
ports: "{{ gitea_ports }}"
|
||||||
|
networks:
|
||||||
|
- name: "{{ gitea_name }}"
|
||||||
|
- name: traefik
|
||||||
|
volumes:
|
||||||
|
- "{{ gitea_root }}:/data"
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
env:
|
||||||
|
USER_UID: "1000"
|
||||||
|
USER_GID: "1000"
|
||||||
|
DB_TYPE: mysql
|
||||||
|
DB_HOST: "{{ gitea_dbname }}"
|
||||||
|
DB_NAME: "{{ gitea_dbname }}"
|
||||||
|
DB_USER: "{{ gitea_dbuser }}"
|
||||||
|
DB_PASSWD: "{{ gitea_dbpass }}"
|
||||||
|
ROOT_URL: "https://{{ gitea_domain }}/"
|
||||||
|
SSH_DOMAIN: "{{ gitea_domain }}"
|
||||||
|
DOMAIN: "{{ gitea_domain }}"
|
||||||
|
labels:
|
||||||
|
traefik.http.routers.gitea.rule: "Host(`{{ gitea_domain }}`)"
|
||||||
|
traefik.http.routers.gitea.entrypoints: websecure
|
||||||
|
traefik.http.routers.gitea.middlewares: "securehttps@file"
|
||||||
|
traefik.http.services.gitea.loadbalancer.server.port: "3000"
|
||||||
|
traefik.docker.network: traefik
|
||||||
|
traefik.enable: "true"
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
# {{ ansible_managed }}
|
|
||||||
gitea_version={{ gitea_version }}
|
|
||||||
gitea_name={{ gitea_name }}
|
|
||||||
gitea_domain={{ gitea_domain }}
|
|
||||||
gitea_rooturl={{ gitea_rooturl }}
|
|
||||||
gitea_web={{ gitea_web }}
|
|
||||||
gitea_ssh={{ gitea_ssh }}
|
|
||||||
gitea_dbtype={{ gitea_dbtype }}
|
|
||||||
gitea_dbhost={{ gitea_dbhost }}
|
|
||||||
gitea_dbname={{ gitea_dbname }}
|
|
||||||
gitea_dbuser={{ gitea_dbuser }}
|
|
||||||
gitea_dbpass={{ gitea_dbpass }}
|
|
||||||
gitea_proxy_limit={{ gitea_proxy_limit }}
|
|
||||||
gitea_trusted_proxies={{ gitea_trusted_proxies }}
|
|
||||||
{% if not gitea_signup %}
|
|
||||||
gitea_disable_registration=true
|
|
||||||
{% else %}
|
|
||||||
gitea_disable_registration=false
|
|
||||||
{% endif %}
|
|
@@ -1,36 +0,0 @@
|
|||||||
version: '3.7'
|
|
||||||
|
|
||||||
services:
|
|
||||||
gitea:
|
|
||||||
image: "gitea/gitea:${gitea_version}"
|
|
||||||
container_name: "${gitea_name}"
|
|
||||||
ports:
|
|
||||||
- "${gitea_ssh}:22"
|
|
||||||
- "${gitea_web}:3000"
|
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
environment:
|
|
||||||
- USER_UID={{ getent_passwd.git[1] }}
|
|
||||||
- USER_GID={{ getent_group.git[1] }}
|
|
||||||
- GITEA__log__MODE=file
|
|
||||||
- GITEA__server__ROOT_URL=${gitea_rooturl}
|
|
||||||
- GITEA__server__DOMAIN=${gitea_domain}
|
|
||||||
- GITEA__server__SSH_DOMAIN=${gitea_domain}
|
|
||||||
- GITEA__database__DB_TYPE=${gitea_dbtype}
|
|
||||||
- GITEA__database__HOST=${gitea_dbhost}
|
|
||||||
- GITEA__database__NAME=${gitea_dbname}
|
|
||||||
- GITEA__database__USER=${gitea_dbuser}
|
|
||||||
- GITEA__database__PASSWD=${gitea_dbpass}
|
|
||||||
- GITEA__security__INSTALL_LOCK=true
|
|
||||||
- GITEA__security__REVERSE_PROXY_LIMIT=${gitea_proxy_limit}
|
|
||||||
- GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES=${gitea_trusted_proxies}
|
|
||||||
- GITEA__service__DISABLE_REGISTRATION=${gitea_disable_registration}
|
|
||||||
volumes:
|
|
||||||
- {{ gitea_volume }}:/data
|
|
||||||
- /home/git/.ssh:/data/git/.ssh
|
|
||||||
- /var/log/gitea:/data/gitea/log
|
|
||||||
- /etc/timezone:/etc/timezone:ro
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
{{ gitea_volume }}:
|
|
@@ -1,4 +0,0 @@
|
|||||||
# {{ ansible_managed }}
|
|
||||||
[Definition]
|
|
||||||
failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
|
|
||||||
ignoreregex =
|
|
@@ -1,18 +0,0 @@
|
|||||||
# {{ ansible_managed }}
|
|
||||||
[gitea]
|
|
||||||
enabled = true
|
|
||||||
filter = gitea
|
|
||||||
logpath = /var/log/gitea/gitea.log
|
|
||||||
maxretry = 10
|
|
||||||
findtime = 3600
|
|
||||||
bantime = 900
|
|
||||||
action = iptables-allports
|
|
||||||
|
|
||||||
[gitea-docker]
|
|
||||||
enabled = true
|
|
||||||
filter = gitea
|
|
||||||
logpath = /var/log/gitea/gitea.log
|
|
||||||
maxretry = 10
|
|
||||||
findtime = 3600
|
|
||||||
bantime = 900
|
|
||||||
action = iptables-allports[chain="FORWARD"]
|
|
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
ssh -p {{ gitea_sshport }} -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" $0 $@"
|
|
1
roles/jenkins/files/ansible.list
Normal file
1
roles/jenkins/files/ansible.list
Normal file
@@ -0,0 +1 @@
|
|||||||
|
deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main
|
@@ -1,3 +1,8 @@
|
|||||||
|
- name: Install GnuPG
|
||||||
|
apt:
|
||||||
|
name: gnupg
|
||||||
|
state: present
|
||||||
|
|
||||||
- name: Create Jenkins user
|
- name: Create Jenkins user
|
||||||
user:
|
user:
|
||||||
name: "{{ jenkins_user }}"
|
name: "{{ jenkins_user }}"
|
||||||
@@ -20,6 +25,16 @@
|
|||||||
validate: "visudo -cf %s"
|
validate: "visudo -cf %s"
|
||||||
mode: 0440
|
mode: 0440
|
||||||
|
|
||||||
|
- name: Install Ansible source
|
||||||
|
copy:
|
||||||
|
src: ansible.list
|
||||||
|
dest: /etc/apt/sources.list.d/ansible.list
|
||||||
|
|
||||||
|
- name: Add Ansible source key
|
||||||
|
apt_key:
|
||||||
|
keyserver: keyserver.ubuntu.com
|
||||||
|
id: 93C4A3FD7BB9C367
|
||||||
|
|
||||||
- name: Install Ansible
|
- name: Install Ansible
|
||||||
apt:
|
apt:
|
||||||
name: ansible
|
name: ansible
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
labels:
|
labels:
|
||||||
traefik.http.routers.jenkins.rule: "Host(`{{ jenkins_domain }}`)"
|
traefik.http.routers.jenkins.rule: "Host(`{{ jenkins_domain }}`)"
|
||||||
traefik.http.routers.jenkins.entrypoints: websecure
|
traefik.http.routers.jenkins.entrypoints: websecure
|
||||||
traefik.http.routers.jenkins.tls.certresolver: letsencrypt
|
|
||||||
traefik.http.routers.jenkins.middlewares: "securehttps@file"
|
traefik.http.routers.jenkins.middlewares: "securehttps@file"
|
||||||
traefik.docker.network: traefik
|
traefik.docker.network: traefik
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
mariadb_trust:
|
|
||||||
- "172.16.0.0/12"
|
|
||||||
- "192.168.0.0/16"
|
|
@@ -1,25 +0,0 @@
|
|||||||
- name: Install MariaDB
|
|
||||||
apt:
|
|
||||||
name: mariadb-server
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Change the bind-address to allow Docker
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/mysql/mariadb.conf.d/50-server.cnf
|
|
||||||
regex: "^bind-address"
|
|
||||||
line: "bind-address = 0.0.0.0"
|
|
||||||
register: mariadb_conf
|
|
||||||
|
|
||||||
- name: Restart MariaDB
|
|
||||||
service:
|
|
||||||
name: mariadb
|
|
||||||
state: restarted
|
|
||||||
when: mariadb_conf.changed
|
|
||||||
|
|
||||||
- name: Allow database connections
|
|
||||||
ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "3306"
|
|
||||||
proto: tcp
|
|
||||||
src: "{{ item }}"
|
|
||||||
loop: "{{ mariadb_trust }}"
|
|
@@ -32,7 +32,6 @@
|
|||||||
labels:
|
labels:
|
||||||
traefik.http.routers.nextcloud.rule: "Host(`{{ nextcloud_domain }}`)"
|
traefik.http.routers.nextcloud.rule: "Host(`{{ nextcloud_domain }}`)"
|
||||||
traefik.http.routers.nextcloud.entrypoints: websecure
|
traefik.http.routers.nextcloud.entrypoints: websecure
|
||||||
traefik.http.routers.nextcloud.tls.certresolver: letsencrypt
|
|
||||||
traefik.http.routers.nextcloud.middlewares: "securehttps@file,nextcloud-webdav"
|
traefik.http.routers.nextcloud.middlewares: "securehttps@file,nextcloud-webdav"
|
||||||
traefik.http.middlewares.nextcloud-webdav.redirectregex.regex: "https://(.*)/.well-known/(card|cal)dav"
|
traefik.http.middlewares.nextcloud-webdav.redirectregex.regex: "https://(.*)/.well-known/(card|cal)dav"
|
||||||
traefik.http.middlewares.nextcloud-webdav.redirectregex.replacement: "https://${1}/remote.php/dav/"
|
traefik.http.middlewares.nextcloud-webdav.redirectregex.replacement: "https://${1}/remote.php/dav/"
|
||||||
|
@@ -31,7 +31,6 @@
|
|||||||
traefik.http.routers.nginx.rule: "Host(`{{ nginx_domain }}`)"
|
traefik.http.routers.nginx.rule: "Host(`{{ nginx_domain }}`)"
|
||||||
#traefik.http.middlewares.nginxauth.basicauth.users: "{{ nginx_auth }}"
|
#traefik.http.middlewares.nginxauth.basicauth.users: "{{ nginx_auth }}"
|
||||||
traefik.http.routers.nginx.entrypoints: websecure
|
traefik.http.routers.nginx.entrypoints: websecure
|
||||||
#traefik.http.routers.nginx.tls.certresolver: letsencrypt
|
traefik.http.routers.nginx.middlewares: "securehttps@file"
|
||||||
#traefik.http.routers.nginx.middlewares: "securehttps@file,nginxauth"
|
|
||||||
traefik.docker.network: traefik
|
traefik.docker.network: traefik
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
postgresql_config: /etc/postgresql/13/main/pg_hba.conf
|
|
||||||
postgresql_listen: "*"
|
|
||||||
postgresql_trust:
|
|
||||||
- "172.16.0.0/12"
|
|
||||||
- "192.168.0.0/16"
|
|
@@ -1,43 +0,0 @@
|
|||||||
- name: Install PostgreSQL
|
|
||||||
apt:
|
|
||||||
name: postgresql
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Trust connections to PostgreSQL
|
|
||||||
postgresql_pg_hba:
|
|
||||||
dest: "{{ postgresql_config }}"
|
|
||||||
contype: host
|
|
||||||
databases: all
|
|
||||||
users: all
|
|
||||||
address: "{{ item }}"
|
|
||||||
method: trust
|
|
||||||
register: postgresql_hba
|
|
||||||
loop: "{{ postgresql_trust }}"
|
|
||||||
|
|
||||||
- name: Change PostgreSQL listen addresses
|
|
||||||
postgresql_set:
|
|
||||||
name: listen_addresses
|
|
||||||
value: "{{ postgresql_listen }}"
|
|
||||||
become: true
|
|
||||||
become_user: postgres
|
|
||||||
register: postgresql_config
|
|
||||||
|
|
||||||
- name: Reload PostgreSQL
|
|
||||||
service:
|
|
||||||
name: postgresql
|
|
||||||
state: reloaded
|
|
||||||
when: postgresql_hba.changed and not postgresql_config.changed
|
|
||||||
|
|
||||||
- name: Restart PostgreSQL
|
|
||||||
service:
|
|
||||||
name: postgresql
|
|
||||||
state: restarted
|
|
||||||
when: postgresql_config.changed
|
|
||||||
|
|
||||||
- name: Allow database connections
|
|
||||||
ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "5432"
|
|
||||||
proto: tcp
|
|
||||||
src: "{{ item }}"
|
|
||||||
loop: "{{ postgresql_trust }}"
|
|
@@ -45,7 +45,6 @@
|
|||||||
traefik.http.routers.prometheus.rule: "Host(`{{ prom_domain }}`)"
|
traefik.http.routers.prometheus.rule: "Host(`{{ prom_domain }}`)"
|
||||||
traefik.http.routers.prometheus.entrypoints: websecure
|
traefik.http.routers.prometheus.entrypoints: websecure
|
||||||
traefik.http.routers.prometheus.middlewares: "securehttps@file,localonly"
|
traefik.http.routers.prometheus.middlewares: "securehttps@file,localonly"
|
||||||
traefik.http.routers.prometheus.tls.certresolver: letsencrypt
|
|
||||||
traefik.http.middlewares.localonly.ipwhitelist.sourcerange: "{{ traefik_localonly }}"
|
traefik.http.middlewares.localonly.ipwhitelist.sourcerange: "{{ traefik_localonly }}"
|
||||||
traefik.docker.network: traefik
|
traefik.docker.network: traefik
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
@@ -65,7 +64,6 @@
|
|||||||
labels:
|
labels:
|
||||||
traefik.http.routers.grafana.rule: "Host(`{{ grafana_domain }}`)"
|
traefik.http.routers.grafana.rule: "Host(`{{ grafana_domain }}`)"
|
||||||
traefik.http.routers.grafana.entrypoints: websecure
|
traefik.http.routers.grafana.entrypoints: websecure
|
||||||
traefik.http.routers.grafana.tls.certresolver: letsencrypt
|
|
||||||
traefik.http.routers.grafana.middlewares: "securehttps@file"
|
traefik.http.routers.grafana.middlewares: "securehttps@file"
|
||||||
traefik.docker.network: traefik
|
traefik.docker.network: traefik
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
systemctl reload nginx
|
|
@@ -1,5 +0,0 @@
|
|||||||
- name: Reload nginx
|
|
||||||
service:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
||||||
listen: reload_nginx
|
|
@@ -1,102 +0,0 @@
|
|||||||
- name: Install nginx
|
|
||||||
apt:
|
|
||||||
name: nginx
|
|
||||||
state: present
|
|
||||||
update_cache: true
|
|
||||||
|
|
||||||
- name: Start nginx and enable on boot
|
|
||||||
service:
|
|
||||||
name: nginx
|
|
||||||
state: started
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
- name: Generate DH Parameters
|
|
||||||
openssl_dhparam:
|
|
||||||
path: /etc/ssl/dhparams.pem
|
|
||||||
size: 4096
|
|
||||||
|
|
||||||
- name: Install nginx base configuration
|
|
||||||
template:
|
|
||||||
src: nginx.conf.j2
|
|
||||||
dest: /etc/nginx/nginx.conf
|
|
||||||
mode: '0644'
|
|
||||||
notify: reload_nginx
|
|
||||||
|
|
||||||
- name: Install nginx sites configuration
|
|
||||||
template:
|
|
||||||
src: server-nginx.conf.j2
|
|
||||||
dest: "/etc/nginx/sites-available/{{ item.domain }}.conf"
|
|
||||||
mode: '0644'
|
|
||||||
loop: "{{ proxy.servers }}"
|
|
||||||
notify: reload_nginx
|
|
||||||
register: nginx_sites
|
|
||||||
|
|
||||||
- name: Enable nginx sites configuration
|
|
||||||
file:
|
|
||||||
src: "/etc/nginx/sites-available/{{ item.item.domain }}.conf"
|
|
||||||
dest: "/etc/nginx/sites-enabled/{{ item.item.domain }}.conf"
|
|
||||||
state: link
|
|
||||||
loop: "{{ nginx_sites.results }}"
|
|
||||||
when: item.changed
|
|
||||||
notify: reload_nginx
|
|
||||||
|
|
||||||
- name: Generate self-signed certificate
|
|
||||||
shell: 'openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes \
|
|
||||||
-subj "/C=US/ST=Local/L=Local/O=Org/OU=IT/CN=example.com" \
|
|
||||||
-keyout /etc/ssl/private/nginx-selfsigned.key \
|
|
||||||
-out /etc/ssl/certs/nginx-selfsigned.crt'
|
|
||||||
args:
|
|
||||||
creates: /etc/ssl/certs/nginx-selfsigned.crt
|
|
||||||
when: proxy.production is not defined or not proxy.production
|
|
||||||
notify: reload_nginx
|
|
||||||
|
|
||||||
- name: Install LE's certbot
|
|
||||||
apt:
|
|
||||||
name: ['certbot', 'python3-certbot-dns-cloudflare']
|
|
||||||
state: present
|
|
||||||
when: proxy.production is defined and proxy.production
|
|
||||||
|
|
||||||
- name: Install Cloudflare API token
|
|
||||||
template:
|
|
||||||
src: cloudflare.ini.j2
|
|
||||||
dest: /root/.cloudflare.ini
|
|
||||||
mode: '0600'
|
|
||||||
when: proxy.production is defined and proxy.production and proxy.dns_cloudflare is defined
|
|
||||||
|
|
||||||
- name: Create nginx post renewal hook directory
|
|
||||||
file:
|
|
||||||
path: /etc/letsencrypt/renewal-hooks/post
|
|
||||||
state: directory
|
|
||||||
when: proxy.production is defined and proxy.production
|
|
||||||
|
|
||||||
- name: Install nginx post renewal hook
|
|
||||||
copy:
|
|
||||||
src: reload-nginx.sh
|
|
||||||
dest: /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
|
|
||||||
mode: '0755'
|
|
||||||
when: proxy.production is defined and proxy.production
|
|
||||||
|
|
||||||
- name: Run Cloudflare DNS-01 challenges on wildcard domains
|
|
||||||
shell: '/usr/bin/certbot certonly \
|
|
||||||
--non-interactive \
|
|
||||||
--agree-tos \
|
|
||||||
--email "{{ proxy.dns_cloudflare.email }}" \
|
|
||||||
--dns-cloudflare \
|
|
||||||
--dns-cloudflare-credentials /root/.cloudflare.ini \
|
|
||||||
-d "*.{{ item }}" \
|
|
||||||
-d "{{ item }}" \
|
|
||||||
{{ proxy.dns_cloudflare.opts | default("") }}'
|
|
||||||
args:
|
|
||||||
creates: "/etc/letsencrypt/live/{{ item }}/fullchain.pem"
|
|
||||||
loop: "{{ proxy.dns_cloudflare.wildcard_domains }}"
|
|
||||||
when: proxy.production is defined and proxy.production and proxy.dns_cloudflare is defined
|
|
||||||
notify: reload_nginx
|
|
||||||
|
|
||||||
- name: Add HTTP and HTTPS firewall rule
|
|
||||||
ufw:
|
|
||||||
rule: allow
|
|
||||||
port: "{{ item }}"
|
|
||||||
proto: tcp
|
|
||||||
loop:
|
|
||||||
- "80"
|
|
||||||
- "443"
|
|
@@ -1,2 +0,0 @@
|
|||||||
# Cloudflare API token used by Certbot
|
|
||||||
dns_cloudflare_api_token = {{ proxy.dns_cloudflare.api_token }}
|
|
@@ -1,34 +0,0 @@
|
|||||||
user www-data;
|
|
||||||
worker_processes auto;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
include /etc/nginx/modules-enabled/*.conf;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] $status '
|
|
||||||
'"$request" $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
server_tokens off;
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
server_names_hash_bucket_size 128;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
ssl_dhparam /etc/ssl/dhparams.pem;
|
|
||||||
ssl_session_cache shared:SSL:10m;
|
|
||||||
ssl_session_timeout 1d;
|
|
||||||
ssl_session_tickets off;
|
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
@@ -1,57 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name {{ item.domain }};
|
|
||||||
return 301 https://{{ item.domain }}$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name {{ item.domain }};
|
|
||||||
access_log /var/log/nginx/{{ item.domain }}.log main;
|
|
||||||
{% if proxy.production is defined and proxy.production and proxy.dns_cloudflare.wildcard_domains is defined and item.tls.cert is not defined %}
|
|
||||||
{% for wildcard in proxy.dns_cloudflare.wildcard_domains %}
|
|
||||||
{% set domain_regex = '^\*\.' + wildcard + '$' %}
|
|
||||||
{% if item.domain | regex_search(wildcard) %}
|
|
||||||
ssl_certificate /etc/letsencrypt/live/{{ wildcard }}/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/{{ wildcard }}/privkey.pem;
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% elif proxy.production is defined and proxy.production and item.tls.cert is not defined %}
|
|
||||||
ssl_certificate /etc/letsencrypt/live/{{ item.domain }}/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/{{ item.domain }}/privkey.pem;
|
|
||||||
{% elif proxy.production is defined and proxy.production and item.tls.cert is defined %}
|
|
||||||
ssl_certificate {{ item.tls.cert }};
|
|
||||||
ssl_certificate_key {{ item.tls.key }};
|
|
||||||
{% else %}
|
|
||||||
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
|
|
||||||
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
|
|
||||||
{% endif %}
|
|
||||||
{% if item.hsts is defined %}
|
|
||||||
add_header Strict-Transport-Security "max-age={{ item.hsts }}" always;
|
|
||||||
{% endif %}
|
|
||||||
{% if item.client_max_body_size is defined %}
|
|
||||||
client_max_body_size {{ item.client_max_body_size }};
|
|
||||||
{% endif %}
|
|
||||||
location / {
|
|
||||||
{% if item.restrict is defined and item.restrict %}
|
|
||||||
auth_basic "{{ item.restrict_name | default('Restricted Access') }}";
|
|
||||||
auth_basic_user_file {{ item.restrict_file | default('/etc/nginx/.htpasswd') }};
|
|
||||||
proxy_set_header Authorization "";
|
|
||||||
{% endif %}
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_pass {{ item.proxy_pass }};
|
|
||||||
{% if item.proxy_ssl_verify is defined and item.proxy_ssl_verify is false %}
|
|
||||||
proxy_ssl_verify off;
|
|
||||||
{% endif %}
|
|
||||||
{% if item.websockets is defined and item.websockets %}
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Connection $http_connection;
|
|
||||||
proxy_set_header Origin http://$host;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
{% endif %}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,10 +3,8 @@ traefik_dashboard: false
|
|||||||
traefik_root: "/opt/{{ traefik_name }}"
|
traefik_root: "/opt/{{ traefik_name }}"
|
||||||
traefik_localonly: "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8"
|
traefik_localonly: "10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8"
|
||||||
traefik_production: false
|
traefik_production: false
|
||||||
traefik_hsts_enable: false
|
|
||||||
traefik_hsts_preload: false
|
traefik_hsts_preload: false
|
||||||
traefik_hsts_seconds: 0
|
traefik_hsts_seconds: 0
|
||||||
traefik_http_redirect: false
|
|
||||||
traefik_ports:
|
traefik_ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
|
@@ -9,6 +9,5 @@
|
|||||||
name: "{{ traefik_name }}"
|
name: "{{ traefik_name }}"
|
||||||
image: traefik:{{ traefik_version }}
|
image: traefik:{{ traefik_version }}
|
||||||
state: started
|
state: started
|
||||||
container_default_behavior: "no_defaults"
|
|
||||||
restart: yes
|
restart: yes
|
||||||
listen: restart_traefik
|
listen: restart_traefik
|
||||||
|
@@ -36,7 +36,6 @@
|
|||||||
state: started
|
state: started
|
||||||
restart_policy: always
|
restart_policy: always
|
||||||
ports: "{{ traefik_ports }}"
|
ports: "{{ traefik_ports }}"
|
||||||
container_default_behavior: "no_defaults"
|
|
||||||
networks_cli_compatible: "false"
|
networks_cli_compatible: "false"
|
||||||
networks:
|
networks:
|
||||||
- name: traefik
|
- name: traefik
|
||||||
@@ -44,8 +43,7 @@
|
|||||||
traefik.http.routers.traefik.rule: "Host(`{{ traefik_domain }}`)"
|
traefik.http.routers.traefik.rule: "Host(`{{ traefik_domain }}`)"
|
||||||
#traefik.http.middlewares.auth.basicauth.users: "{{ traefik_auth }}"
|
#traefik.http.middlewares.auth.basicauth.users: "{{ traefik_auth }}"
|
||||||
#traefik.http.middlewares.localonly.ipwhitelist.sourcerange: "{{ traefik_localonly }}"
|
#traefik.http.middlewares.localonly.ipwhitelist.sourcerange: "{{ traefik_localonly }}"
|
||||||
#traefik.http.routers.traefik.tls.certresolver: letsencrypt
|
traefik.http.routers.traefik.middlewares: "securehttps@file"
|
||||||
#traefik.http.routers.traefik.middlewares: "securehttps@file,auth@docker,localonly"
|
|
||||||
traefik.http.routers.traefik.service: "api@internal"
|
traefik.http.routers.traefik.service: "api@internal"
|
||||||
traefik.http.routers.traefik.entrypoints: websecure
|
traefik.http.routers.traefik.entrypoints: websecure
|
||||||
traefik.http.routers.traefik.tls: "true"
|
traefik.http.routers.traefik.tls: "true"
|
||||||
|
@@ -10,12 +10,10 @@ http:
|
|||||||
{% elif item.middlewares is defined %}
|
{% elif item.middlewares is defined %}
|
||||||
middlewares: "{{ item.middlewares }}"
|
middlewares: "{{ item.middlewares }}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if traefik_acme_email is defined %}
|
|
||||||
tls:
|
tls:
|
||||||
certResolver: letsencrypt
|
certResolver: letsencrypt
|
||||||
domains:
|
domains:
|
||||||
- main: "{{ item.domain }}"
|
- main: "{{ item.domain }}"
|
||||||
{% endif %}
|
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- "websecure"
|
- "websecure"
|
||||||
services:
|
services:
|
||||||
|
@@ -11,8 +11,6 @@ http:
|
|||||||
sslRedirect: true
|
sslRedirect: true
|
||||||
browserXssFilter: true
|
browserXssFilter: true
|
||||||
contentTypeNosniff: true
|
contentTypeNosniff: true
|
||||||
{% if traefik_hsts_enable is defined and traefik_hsts_enable %}
|
|
||||||
stsPreload: {{ traefik_hsts_preload }}
|
stsPreload: {{ traefik_hsts_preload }}
|
||||||
stsSeconds: {{ traefik_hsts_seconds }}
|
stsSeconds: {{ traefik_hsts_seconds }}
|
||||||
{% endif %}
|
|
||||||
customFrameOptionsValue: SAMEORIGIN
|
customFrameOptionsValue: SAMEORIGIN
|
||||||
|
@@ -10,14 +10,12 @@ providers:
|
|||||||
entrypoints:
|
entrypoints:
|
||||||
web:
|
web:
|
||||||
address: ':80'
|
address: ':80'
|
||||||
{% if traefik_http_redirect is defined and traefik_http_redirect %}
|
|
||||||
http:
|
http:
|
||||||
redirections:
|
redirections:
|
||||||
entrypoint:
|
entrypoint:
|
||||||
to: websecure
|
to: websecure
|
||||||
scheme: https
|
scheme: https
|
||||||
permanent: true
|
permanent: true
|
||||||
{% endif %}
|
|
||||||
websecure:
|
websecure:
|
||||||
address: ':443'
|
address: ':443'
|
||||||
http:
|
http:
|
||||||
|
Reference in New Issue
Block a user