From cc834df161bf6192c30331d4691b1cfda82396b5 Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Sun, 28 Nov 2021 16:38:52 -0500 Subject: [PATCH] added partkeepr, motion, and relevant secrets --- ansible/deploy_home.yml | 2 + ansible/roles/common/defaults/main.yml | 1 + ansible/roles/http/defaults/main.yml | 1 + ansible/roles/http/tasks/http.yml | 2 + .../nginx/sites/home.bdebyl.net.conf.j2 | 2 +- .../nginx/sites/parts.bdebyl.net.conf.j2 | 22 +++++ ansible/roles/motion/tasks/motion.yml | 31 ++++++- ansible/roles/motion/templates/motion.conf.j2 | 12 +-- .../templates/motion.service.override.j2 | 2 + ansible/roles/partkeepr/meta/main.yml | 3 + ansible/roles/partkeepr/tasks/main.yml | 77 ++++++++++++++++++ ansible/roles/pihole/defaults/main.yml | 5 ++ ansible/roles/pihole/meta/main.yml | 3 + ansible/roles/pihole/tasks/deps.yml | 11 +++ ansible/roles/pihole/tasks/main.yml | 3 + ansible/vars/vault.yml | Bin 3098 -> 3293 bytes 16 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 ansible/roles/http/templates/nginx/sites/parts.bdebyl.net.conf.j2 create mode 100644 ansible/roles/motion/templates/motion.service.override.j2 create mode 100644 ansible/roles/partkeepr/meta/main.yml create mode 100644 ansible/roles/partkeepr/tasks/main.yml create mode 100644 ansible/roles/pihole/defaults/main.yml create mode 100644 ansible/roles/pihole/meta/main.yml create mode 100644 ansible/roles/pihole/tasks/deps.yml create mode 100644 ansible/roles/pihole/tasks/main.yml diff --git a/ansible/deploy_home.yml b/ansible/deploy_home.yml index 57b2fb5..109832e 100644 --- a/ansible/deploy_home.yml +++ b/ansible/deploy_home.yml @@ -13,3 +13,5 @@ tags: nfs - role: motion tags: motion + - role: partkeepr + tags: partkeepr \ No newline at end of file diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml index d9a1cda..42e8843 100644 --- a/ansible/roles/common/defaults/main.yml +++ b/ansible/roles/common/defaults/main.yml @@ -20,3 +20,4 @@ services: - fail2ban - iptables - nginx + - systemd-timesyncd diff --git a/ansible/roles/http/defaults/main.yml b/ansible/roles/http/defaults/main.yml index eba3a34..41ab50e 100644 --- a/ansible/roles/http/defaults/main.yml +++ b/ansible/roles/http/defaults/main.yml @@ -7,6 +7,7 @@ deps: [ ci_server_name: ci.bdebyl.net home_server_name: home.bdebyl.net +parts_server_name: parts.bdebyl.net install_path: /usr/share nginx_path: /etc/nginx diff --git a/ansible/roles/http/tasks/http.yml b/ansible/roles/http/tasks/http.yml index a357025..73a6f9f 100644 --- a/ansible/roles/http/tasks/http.yml +++ b/ansible/roles/http/tasks/http.yml @@ -37,6 +37,7 @@ loop: - "{{ ci_server_name }}.http.conf" - "{{ home_server_name }}.conf" + - "{{ parts_server_name }}.conf" notify: restart_nginx tags: http @@ -48,5 +49,6 @@ state: link loop: - "{{ ci_server_name }}.http.conf" + - "{{ parts_server_name }}.conf" notify: restart_nginx tags: http diff --git a/ansible/roles/http/templates/nginx/sites/home.bdebyl.net.conf.j2 b/ansible/roles/http/templates/nginx/sites/home.bdebyl.net.conf.j2 index f7fd258..5c61136 100644 --- a/ansible/roles/http/templates/nginx/sites/home.bdebyl.net.conf.j2 +++ b/ansible/roles/http/templates/nginx/sites/home.bdebyl.net.conf.j2 @@ -1,6 +1,6 @@ geo $whitelisted { default 0; - 192.168.1.1/24 1; + 192.168.1.0/24 1; } server { diff --git a/ansible/roles/http/templates/nginx/sites/parts.bdebyl.net.conf.j2 b/ansible/roles/http/templates/nginx/sites/parts.bdebyl.net.conf.j2 new file mode 100644 index 0000000..dfd4b67 --- /dev/null +++ b/ansible/roles/http/templates/nginx/sites/parts.bdebyl.net.conf.j2 @@ -0,0 +1,22 @@ +geo $whitelisted { + default 0; + 192.168.1.0/24 1; +} + +upstream partkeepr { + server localhost:8081; +} + +server { + listen 80; + server_name {{ parts_server_name }}; + + if ($whitelisted = 0) { + return 302 $scheme://bdebyl.net$request_uri; + } + + location / { + proxy_pass http://partkeepr; + proxy_connect_timeout 1s; + } +} diff --git a/ansible/roles/motion/tasks/motion.yml b/ansible/roles/motion/tasks/motion.yml index ad81238..4be23e5 100644 --- a/ansible/roles/motion/tasks/motion.yml +++ b/ansible/roles/motion/tasks/motion.yml @@ -1,4 +1,13 @@ --- +- name: give motion user nfs permissions + become: true + user: + name: motion + groups: "{{ nfs_group }}" + append: true + notify: + - restart_motion + - name: create motion directory become: true file: @@ -6,7 +15,7 @@ state: directory owner: "{{ nfs_user }}" group: "{{ nfs_group }}" - mode: 0755 + mode: 0777 - name: template motion config become: true @@ -18,9 +27,25 @@ notify: - restart_motion +- name: create motion systemd override directory + become: true + file: + path: /etc/systemd/system/motion.service.d/ + state: directory + mode: 0644 + +- name: template motion systemd override + become: true + template: + src: templates/motion.service.override.j2 + dest: /etc/systemd/system/motion.service.d/override.conf + mode: 0644 + notify: + - restart_motion + - name: enable (now) motion.service become: true service: name: motion.service - state: started - enabled: true + state: stopped + enabled: false diff --git a/ansible/roles/motion/templates/motion.conf.j2 b/ansible/roles/motion/templates/motion.conf.j2 index 993d09b..1249a58 100644 --- a/ansible/roles/motion/templates/motion.conf.j2 +++ b/ansible/roles/motion/templates/motion.conf.j2 @@ -37,7 +37,7 @@ target_dir {{ motion_target_dir }} ; vid_control_params value # The full URL of the network camera stream. -netcam_url rtsp://{{ motion_user }}:{{ motion_pass }}@{{ motion_hostname }}:{{ motion_port }}/mpeg4/media.amp +netcam_url rtsp://{{ motion_hostname }}:{{ motion_port }}/h264?username={{ motion_user }}&password={{ motion_pass }} # Name of mmal camera (e.g. vc.ril.camera for pi camera). ; mmalcam_name value @@ -87,10 +87,10 @@ minimum_motion_frames 1 event_gap 60 # The number of pre-captured (buffered) pictures from before motion. -pre_capture 3 +pre_capture 80 # Number of frames to capture after motion is no longer detected. -post_capture 0 +post_capture 300 ############################################################ # Script execution configuration parameters @@ -123,16 +123,16 @@ picture_filename %Y%m%d%H%M%S-%q movie_output on # Maximum length of movie in seconds. -movie_max_time 60 +movie_max_time 30 # The encoding quality of the movie. (0=use bitrate. 1=worst quality, 100=best) movie_quality 45 # Container/Codec to used for the movie. See motion_guide.html -movie_codec mkv +movie_codec mp4 # File name(without extension) for movies relative to target directory -movie_filename %t-%v-%Y%m%d%H%M%S +movie_filename %Y%m%d-%H_%M_%S ############################################################ # Webcontrol configuration parameters diff --git a/ansible/roles/motion/templates/motion.service.override.j2 b/ansible/roles/motion/templates/motion.service.override.j2 new file mode 100644 index 0000000..78422bb --- /dev/null +++ b/ansible/roles/motion/templates/motion.service.override.j2 @@ -0,0 +1,2 @@ +[Service] +User={{ nfs_user }} diff --git a/ansible/roles/partkeepr/meta/main.yml b/ansible/roles/partkeepr/meta/main.yml new file mode 100644 index 0000000..3f81c4b --- /dev/null +++ b/ansible/roles/partkeepr/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role: http diff --git a/ansible/roles/partkeepr/tasks/main.yml b/ansible/roles/partkeepr/tasks/main.yml new file mode 100644 index 0000000..d019e82 --- /dev/null +++ b/ansible/roles/partkeepr/tasks/main.yml @@ -0,0 +1,77 @@ +--- +- name: create required partkeepr volumes + docker_volume: + name: "{{ item }}" + with_items: + - parkeepr-web-vol + - partkeepr-conf-vol + - partkeepr-data-vol + - partkeepr-db-vol + +- name: create partkeepr-db container + diff: false + docker_container: + name: partkeepr-db + image: mariadb:10.0 + recreate: false + restart: true + restart_policy: on-failure + restart_retries: 3 + env: + MYSQL_RANDOM_ROOT_PASSWORD: 'yes' + MYSQL_DATABASE: partkeepr + MYSQL_USER: partkeepr + MYSQL_PASSWORD: partkeepr + volumes: + - partkeepr-db-vol:/var/lib/mysql + + + # 'PARTKEEPR_AUTHENTICATION_PROVIDER': 'PartKeepr.Auth.WSSEAuthenticationProvider' + # 'PARTKEEPR_CACHE_DOCTRINE': 'array' + # 'PARTKEEPR_CACHE_DUNGLAS': False + # 'PARTKEEPR_DATABASE_DRIVER': 'pdo_mysql' + # 'PARTKEEPR_DATABASE_HOST': 'database' + # 'PARTKEEPR_DATABASE_NAME': 'partkeepr' + # 'PARTKEEPR_DATABASE_PORT': 3306 + # 'PARTKEEPR_DATABASE_USER': 'partkeepr' + # 'PARTKEEPR_DATABASE_PASS': 'partkeepr' + # 'PARTKEEPR_FR3D_LDAP_DRIVER_ACCOUNTCANONICALFORM': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_ACCOUNTDOMAINNAME': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_ACCOUNTDOMAINNAMESHORT': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_ACCOUNTFILTERFORMAT': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_BASEDN': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_BINDREQUIRESDN': False + # 'PARTKEEPR_FR3D_LDAP_DRIVER_HOST': '127.0.0.1' + # 'PARTKEEPR_FR3D_LDAP_DRIVER_OPTREFERRALS': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_PASSWORD': NULL + # 'PARTKEEPR_FR3D_LDAP_DRIVER_PORT': 389 + # 'PARTKEEPR_FR3D_LDAP_DRIVER_USESSL': False + # 'PARTKEEPR_FR3D_LDAP_DRIVER_USESTARTTLS': False + # 'PARTKEEPR_FR3D_LDAP_DRIVER_USERNAME': NULL + # 'PARTKEEPR_FR3D_LDAP_USER_ATTRIBUTE_EMAIL': 'email' + # 'PARTKEEPR_FR3D_LDAP_USER_ATTRIBUTE_USERNAME': 'samaccountname' + # 'PARTKEEPR_FR3D_LDAP_USER_BASEDN': 'dc=example,dc=com' + # 'PARTKEEPR_FR3D_LDAP_USER_ENABLED': False + # 'PARTKEEPR_FR3D_LDAP_USER_FILTER': NULL + # 'PARTKEEPR_LOCALE': 'en' + # 'PARTKEEPR_MAILER_AUTH_MODE': NULL + # 'PARTKEEPR_MAILER_ENCRYPTION': NULL + # 'PARTKEEPR_MAILER_HOST': NULL + # 'PARTKEEPR_MAILER_PASSWORD': NULL + # 'PARTKEEPR_MAILER_PORT': 25 + # 'PARTKEEPR_MAILER_TRANSPORT': NULL + # 'PARTKEEPR_MAILER_USER': NULL + # 'PARTKEEPR_AUTH_MAX_USERS': 'unlimited' + # 'PARTKEEPR_CATEGORY_PATH_SEPARATOR': ' ➤ ' + # 'PARTKEEPR_CRONJOB_CHECK': True + # 'PARTKEEPR_FILESYSTEM_DATA_DIRECTORY': '%kernel.root_dir%/../data/' + # 'PARTKEEPR_FILESYSTEM_QUOTA': False + # 'PARTKEEPR_MAINTENANCE': false + # 'PARTKEEPR_MAINTENANCE_MESSAGE': NULL + # 'PARTKEEPR_MAINTENANCE_TITLE': NULL + # 'PARTKEEPR_OCTOPART_APIKEY': NULL + # 'PARTKEEPR_PARTS_INTERNALPARTNUMBERUNIQUE': False + # 'PARTKEEPR_PARTS_LIMIT': False + # 'PARTKEEPR_USERS_LIMIT': False + # 'PARTKEEPR_SECRET': 'OJBKOJIKNONAJENLBJJNLFIDPDGKDIED' + # volumes: \ No newline at end of file diff --git a/ansible/roles/pihole/defaults/main.yml b/ansible/roles/pihole/defaults/main.yml new file mode 100644 index 0000000..26a6ab1 --- /dev/null +++ b/ansible/roles/pihole/defaults/main.yml @@ -0,0 +1,5 @@ +--- +deps: [ + php-sqlite, + php-fpm +] diff --git a/ansible/roles/pihole/meta/main.yml b/ansible/roles/pihole/meta/main.yml new file mode 100644 index 0000000..3f81c4b --- /dev/null +++ b/ansible/roles/pihole/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role: http diff --git a/ansible/roles/pihole/tasks/deps.yml b/ansible/roles/pihole/tasks/deps.yml new file mode 100644 index 0000000..323b55c --- /dev/null +++ b/ansible/roles/pihole/tasks/deps.yml @@ -0,0 +1,11 @@ +--- +- name: install pi-hole-server + command: yay -S --noconfirm pi-hole-server + args: + creates: /bin/pihole + +- name: install pi-hole-server dependencies + become: true + pacman: + name: "{{ deps }}" + state: present diff --git a/ansible/roles/pihole/tasks/main.yml b/ansible/roles/pihole/tasks/main.yml new file mode 100644 index 0000000..736d254 --- /dev/null +++ b/ansible/roles/pihole/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: deps.yml +- import_tasks: php.yml diff --git a/ansible/vars/vault.yml b/ansible/vars/vault.yml index 3c3bca973558e1bf9fb676585d735743d5578793..511ae70da2ed2e5962e92712700ae4352fbb8419 100644 GIT binary patch literal 3293 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 b_-r`eg!fMTNnan2VG*=45p|@i_#Ap_V=8$D literal 3098 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 oRi9*2{!CN+6>2F(6Hq)$