Browse Source

feat: cleanup ansible

pull/3/head
Karan Sharma 2 years ago
parent
commit
3e4058b249
  1. 3
      .gitignore
  2. 17
      README.md
  3. 15
      k8s/adguard/Makefile
  4. 44
      k8s/adguard/base/adguard/adguard-deployment.yml
  5. 22
      k8s/adguard/base/adguard/adguard-dns-service.yml
  6. 22
      k8s/adguard/base/adguard/adguard-service.yml
  7. 18
      k8s/adguard/base/adguard/adguard-web-service.yml
  8. 88
      k8s/adguard/base/configs/Adguard.tmpl.yml
  9. 15
      k8s/adguard/base/kustomization.yml
  10. 4
      k8s/adguard/base/namespace.yml
  11. 23
      k8s/adguard/base/patches/add-config-volume.yml
  12. 23
      k8s/adguard/base/patches/init-container.yml
  13. 18
      k8s/adguard/base/patches/nodeport-hardcode.yml
  14. 60
      k8s/adguard/kubekutr.yml
  15. 225
      k8s/adguard/t.yml
  16. 2
      k8s/ip/base/nginx-ip/nginx-deployment.yml
  17. 2
      k8s/ip/kubekutr.yml
  18. 2
      pi/inventory.sample
  19. 14
      pi/roles/k3s-control/tasks/control.yml
  20. 1
      pi/roles/k3s-control/templates/k3s-control.service.j2
  21. 89
      pi/roles/raspbian/vars/secret.yml
  22. 3
      pi/setup.yml

3
.gitignore

@ -8,4 +8,5 @@ inventory
*.tfstate
*.tfstate.backup
*.backup
*.env
*.env
AdGuardHome.yaml

17
README.md

@ -1,4 +1,5 @@
# Hydra
> Setup scripts for my home server setup named "Hydra"
## Overview
@ -19,22 +20,22 @@ If you're interested in bootstrapping a RPi cluster with k3s, you can refer to t
- Copy the `inventory.sample` to `inventory` and replace the hosts with your RPi nodes.
- Replace the `pi/roles/raspbian/vars/secret.sample` with your own secrets in `pi/roles/raspbian/vars/secret.yml`. I generally prefer to use Ansible Vault to encrypt the secrets.
- Run the playbook using `ansible-playbook pi/setup.yml`. This will configure your RPi for:
- Changing the default password for `pi`.
- Disabling Password based SSH access.
- Configure hostname on all RPi nodes.
- Enable `cgroups` features.
- Update the GPU memory to lowest possible (16M) since we are going to use this as a server.
- Bootstrap a k3s cluster on the control-plane node and join a agent node with the control-plane node.
- Changing the default password for `pi`.
- Disabling Password based SSH access.
- Configure hostname on all RPi nodes.
- Enable `cgroups` features.
- Update the GPU memory to lowest possible (16M) since we are going to use this as a server.
- Bootstrap a k3s cluster on the control-plane node and join a agent node with the control-plane node.
### Credits
> I referred to the following resources while creating the Playbook
- [https://blog.alexellis.io/test-drive-k3s-on-raspberry-pi/](https://blog.alexellis.io/test-drive-k3s-on-raspberry-pi/)
- [https://github.com/rancher/k3s/tree/master/contrib/ansible/](https://github.com/rancher/k3s/tree/master/contrib/ansible/)
## Services Hosted
> List of services I am running on Hydra.
WIP

15
k8s/adguard/Makefile

@ -0,0 +1,15 @@
include base/configs/password.env
export
.PHONY: scaffold
scaffold:
kubekutr -c kubekutr.yml scaffold -o .
.PHONY: build-k8s-local
build-k8s-local: scaffold
@envsubst < base/configs/Adguard.tmpl.yml | tee base/configs/AdGuardHome.yaml > /dev/null
@kustomize build base --load_restrictor none
.PHONY: deploy-k8s-local
deploy-k8s-local: build-k8s-local
kustomize build base/ --load_restrictor none | kubectl apply -f -

44
k8s/adguard/base/adguard/adguard-deployment.yml

@ -0,0 +1,44 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguard
labels:
service: adguard
tier: dns
spec:
replicas: 1
selector:
matchLabels:
service: adguard
tier: dns
template:
metadata:
labels:
service: adguard
tier: dns
spec:
containers:
- name: adguard
image: adguard/adguardhome:armhf-edge
ports:
- containerPort: 53
name: dns-port
ports:
- containerPort: 3000
name: web-port
resources:
requests:
memory: 100Mi
cpu: 90m
limits:
memory: 200Mi
cpu: 180m
volumeMounts:
- mountPath: /config
name: tmp-config-volume
- mountPath: /app
name: config-volume
volumes:
- name: tmp-config-volume
- name: config-volume

22
k8s/adguard/base/adguard/adguard-dns-service.yml

@ -0,0 +1,22 @@
---
apiVersion: v1
kind: Service
metadata:
name: adguard-dns
labels:
service: adguard
tier: dns
spec:
ports:
- port: 53
name: dot-port
targetPort: dns-port
protocol: TCP
- port: 53
name: dns-port
targetPort: dns-port
protocol: UDP
type: NodePort
selector:
tier: dns
service: adguard

22
k8s/adguard/base/adguard/adguard-service.yml

@ -0,0 +1,22 @@
---
apiVersion: v1
kind: Service
metadata:
name: adguard
labels:
service: adguard
tier: dns
spec:
ports:
- port: 53
name: dns-port
targetPort: dns-port
protocol: TCP
- port: 3000
name: web-port
targetPort: web-port
protocol: TCP
type: ClusterIP
selector:
service: adguard
tier: dns

18
k8s/adguard/base/adguard/adguard-web-service.yml

@ -0,0 +1,18 @@
---
apiVersion: v1
kind: Service
metadata:
name: adguard-web
labels:
service: adguard
tier: web
spec:
ports:
- port: 3000
name: web-port
targetPort: web-port
protocol: TCP
type: NodePort
selector:
tier: dns
service: adguard

88
k8s/adguard/base/configs/Adguard.tmpl.yml

@ -0,0 +1,88 @@
bind_host: 0.0.0.0
bind_port: 3000
users:
- name: karan
password: ${ADGUARD_PASSWORD}
language: ""
rlimit_nofile: 0
web_session_ttl: 720
dns:
bind_host: 0.0.0.0
port: 53
statistics_interval: 1
querylog_enabled: true
querylog_interval: 90
querylog_memsize: 0
protection_enabled: true
blocking_mode: null_ip
blocking_ipv4: ""
blocking_ipv6: ""
blocked_response_ttl: 10
ratelimit: 20
ratelimit_whitelist: []
refuse_any: true
bootstrap_dns: []
all_servers: false
edns_client_subnet: false
allowed_clients: []
disallowed_clients: []
blocked_hosts: []
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
cache_size: 4194304
upstream_dns:
- 1.1.1.1
filtering_enabled: true
filters_update_interval: 24
parental_sensitivity: 0
parental_enabled: false
safesearch_enabled: false
safebrowsing_enabled: false
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
rewrites: []
blocked_services: []
tls:
enabled: false
server_name: ""
force_https: false
port_https: 443
port_dns_over_tls: 853
allow_unencrypted_doh: false
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
filters:
- enabled: true
url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
name: AdGuard Simplified Domain Names filter
id: 1
- enabled: true
url: https://adaway.org/hosts.txt
name: AdAway
id: 2
- enabled: true
url: https://hosts-file.net/ad_servers.txt
name: hpHosts - Ad and Tracking servers only
id: 3
- enabled: true
url: https://www.malwaredomainlist.com/hostslist/hosts.txt
name: MalwareDomainList.com Hosts List
id: 4
user_rules: []
dhcp:
enabled: false
interface_name: ""
gateway_ip: ""
subnet_mask: ""
range_start: ""
range_end: ""
lease_duration: 86400
icmp_timeout_msec: 1000
clients: []
log_file: ""
verbose: false
schema_version: 6

15
k8s/adguard/base/kustomization.yml

@ -0,0 +1,15 @@
namespace: adguard
resources:
- namespace.yml
- adguard/adguard-deployment.yml
- adguard/adguard-dns-service.yml
- adguard/adguard-service.yml
- adguard/adguard-web-service.yml
configMapGenerator:
- name: app-config
files:
- AdGuardHome.yaml=configs/AdGuardHome.yaml
patchesStrategicMerge:
- patches/init-container.yml
- patches/add-config-volume.yml
- patches/nodeport-hardcode.yml

4
k8s/adguard/base/namespace.yml

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: adguard

23
k8s/adguard/base/patches/add-config-volume.yml

@ -0,0 +1,23 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguard
spec:
template:
spec:
volumes:
- name: tmp-config-volume
configMap:
name: app-config
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguard
spec:
template:
spec:
volumes:
- name: config-volume
emptyDir: {}

23
k8s/adguard/base/patches/init-container.yml

@ -0,0 +1,23 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguard
labels:
service: adguard
spec:
template:
spec:
initContainers:
- name: init-adguard
image: balenalib/armv7hf-debian:run
command: [
"sh",
"-c",
"cp /config/AdGuardHome.yaml /app"
]
volumeMounts:
- name: tmp-config-volume
mountPath: "/config"
- name: config-volume
mountPath: "/app"

18
k8s/adguard/base/patches/nodeport-hardcode.yml

@ -0,0 +1,18 @@
# ---
# apiVersion: v1
# kind: Service
# metadata:
# name: adguard-dns
# spec:
# ports:
# - port: 53
# nodePort: 30100
---
apiVersion: v1
kind: Service
metadata:
name: adguard-web
spec:
ports:
- port: 3000
nodePort: 30102

60
k8s/adguard/kubekutr.yml

@ -0,0 +1,60 @@
workloads:
- name: adguard
deployments:
- name: adguard
replicas: 1
labels:
- name: 'service: adguard'
- name: 'tier: dns'
containers:
- name: adguard
createService: true
image: 'adguard/adguardhome:armhf-edge'
ports:
- name: dns-port
port: 53
- name: web-port
port: 3000
cpuLimits: 180m
memoryLimits: 200Mi
cpuRequests: 90m
memoryRequests: 100Mi
volumeMounts:
- name: tmp-config-volume
mountPath: /config
- name: config-volume
mountPath: /app
volumes:
- name: tmp-config-volume
- name: config-volume
services:
- name: adguard-dns
type: NodePort
ports:
- name: dot-port
targetPort: dns-port
port: 53
protocol: TCP
- name: dns-port
targetPort: dns-port
port: 53
protocol: UDP
labels:
- name: 'service: adguard'
- name: 'tier: dns'
selectors:
- name: 'tier: dns'
- name: 'service: adguard'
- name: adguard-web
type: NodePort
ports:
- name: web-port
targetPort: web-port
port: 3000
labels:
- name: 'service: adguard'
- name: 'tier: web'
selectors:
- name: 'tier: dns'
- name: 'service: adguard'

225
k8s/adguard/t.yml

@ -0,0 +1,225 @@
kubekutr -c kubekutr.yml scaffold -o .
apiVersion: v1
kind: Namespace
metadata:
name: adguard
---
apiVersion: v1
data:
AdGuardHome.yaml: |-
bind_host: 0.0.0.0
bind_port: 3000
users:
- name: karan
password: bullah
language: ""
rlimit_nofile: 0
web_session_ttl: 720
dns:
bind_host: 0.0.0.0
port: 53
statistics_interval: 1
querylog_enabled: true
querylog_interval: 90
querylog_memsize: 0
protection_enabled: true
blocking_mode: null_ip
blocking_ipv4: ""
blocking_ipv6: ""
blocked_response_ttl: 10
ratelimit: 20
ratelimit_whitelist: []
refuse_any: true
bootstrap_dns: []
all_servers: false
edns_client_subnet: false
allowed_clients: []
disallowed_clients: []
blocked_hosts: []
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
cache_size: 4194304
upstream_dns:
- 1.1.1.1
filtering_enabled: true
filters_update_interval: 24
parental_sensitivity: 0
parental_enabled: false
safesearch_enabled: false
safebrowsing_enabled: false
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
rewrites: []
blocked_services: []
tls:
enabled: false
server_name: ""
force_https: false
port_https: 443
port_dns_over_tls: 853
allow_unencrypted_doh: false
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
filters:
- enabled: true
url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
name: AdGuard Simplified Domain Names filter
id: 1
- enabled: true
url: https://adaway.org/hosts.txt
name: AdAway
id: 2
- enabled: true
url: https://hosts-file.net/ad_servers.txt
name: hpHosts - Ad and Tracking servers only
id: 3
- enabled: true
url: https://www.malwaredomainlist.com/hostslist/hosts.txt
name: MalwareDomainList.com Hosts List
id: 4
user_rules: []
dhcp:
enabled: false
interface_name: ""
gateway_ip: ""
subnet_mask: ""
range_start: ""
range_end: ""
lease_duration: 86400
icmp_timeout_msec: 1000
clients: []
log_file: ""
verbose: false
schema_version: 6
kind: ConfigMap
metadata:
name: app-config-54ckbfb94d
namespace: adguard
---
apiVersion: v1
kind: Service
metadata:
labels:
service: adguard
tier: dns
name: adguard
namespace: adguard
spec:
ports:
- name: dns-port
port: 53
protocol: TCP
targetPort: dns-port
- name: web-port
port: 3000
protocol: TCP
targetPort: web-port
selector:
service: adguard
tier: dns
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
labels:
service: adguard
tier: dns
name: adguard-dns
namespace: adguard
spec:
ports:
- name: dot-port
nodePort: 30100
port: 53
protocol: TCP
targetPort: dns-port
- name: dns-port
port: 53
protocol: UDP
targetPort: dns-port
selector:
service: adguard
tier: dns
type: NodePort
---
apiVersion: v1
kind: Service
metadata:
labels:
service: adguard
tier: web
name: adguard-web
namespace: adguard
spec:
ports:
- name: web-port
nodePort: 30102
port: 3000
protocol: TCP
targetPort: web-port
selector:
service: adguard
tier: dns
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
service: adguard
tier: dns
name: adguard
namespace: adguard
spec:
replicas: 1
selector:
matchLabels:
service: adguard
tier: dns
template:
metadata:
labels:
service: adguard
tier: dns
spec:
containers:
- image: adguard/adguardhome:armhf-edge
name: adguard
ports:
- containerPort: 3000
name: web-port
resources:
limits:
cpu: 180m
memory: 200Mi
requests:
cpu: 90m
memory: 100Mi
volumeMounts:
- mountPath: /config
name: tmp-config-volume
- mountPath: /app
name: config-volume
initContainers:
- command:
- sh
- -c
- cp /config/AdGuardHome.yaml /app
image: balenalib/armv7hf-debian:run
name: init-adguard
volumeMounts:
- mountPath: /config
name: tmp-config-volume
- mountPath: /app
name: config-volume
volumes:
- emptyDir: {}
name: config-volume
- configMap:
name: app-config-54ckbfb94d
name: tmp-config-volume

2
k8s/ip/base/nginx-ip/nginx-deployment.yml

@ -7,7 +7,7 @@ metadata:
service: nginx
tier: proxy
spec:
replicas: 3
replicas: 2
selector:
matchLabels:
service: nginx

2
k8s/ip/kubekutr.yml

@ -2,7 +2,7 @@ workloads:
- name: nginx-ip
deployments:
- name: nginx
replicas: 3
replicas: 2
labels:
- name: 'service: nginx'
- name: 'tier: proxy'

2
pi/inventory.sample

@ -6,4 +6,4 @@ agent
hydra-control ansible_ssh_host=hydra-control.local ansible_ssh_user=pi ansible_ssh_port=22 remote_user=pi
[agent]
hydra-agent-1 ansible_ssh_host=raspberrypi.local ansible_ssh_user=pi ansible_ssh_port=22 remote_user=pi ansible_ssh_pass=raspberry
hydra-agent-1 ansible_ssh_host=hydra-agent-1.local ansible_ssh_user=pi ansible_ssh_port=22 remote_user=pi ansible_ssh_pass=raspberry

14
pi/roles/k3s-control/tasks/control.yml

@ -48,12 +48,12 @@
path: /var/lib/rancher/k3s/server
mode: "{{ p.stat.mode }}"
- name: Copy config file to user home directory
copy:
src: /etc/rancher/k3s/k3s.yaml
dest: /home/{{ ansible_user }}/.kube/config
remote_src: yes
owner: "{{ ansible_user }}"
# - name: Copy config file to user home directory
# copy:
# src: /etc/rancher/k3s/k3s.yaml
# dest: /home/{{ ansible_user }}/.kube/config
# remote_src: yes
# owner: "{{ ansible_user }}"
- name: Set server address
set_fact:
@ -68,4 +68,4 @@
replace:
path: /home/{{ ansible_user }}/.kube/config
regexp: 'https://localhost:6443'
replace: 'https://{{k3s_server_address}}'
replace: '{{k3s_server_address}}'

1
pi/roles/k3s-control/templates/k3s-control.service.j2

@ -9,6 +9,7 @@ ExecStartPre=-/sbin/modprobe br_netfilter
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/k3s \
server \
--write-kubeconfig 644
KillMode=process
Delegate=yes

89
pi/roles/raspbian/vars/secret.yml

@ -1,46 +1,45 @@
$ANSIBLE_VAULT;1.1;AES256
38633337333137666632303434383435616335313665396334313235623933326330623438613966
6336323837323136323063396137316336313665323133370a653138656137353037386335346532
38623634353134643534326634653866653937343034316462633634393664633436623464393065
6534326131313066360a623134383135653563373938303436326331313735386634636463386238
62623034653533346463623431363338636666323437356635343338363733353133306232306366
33326537396139353963336539336635326364313938353563373933633865386135353361373138
38633134326134343830303433323533353635376230386332393662616262306365393065653632
34663432313033623337633038643031313438656664316237333464643731653836646235373463
36363764363861653935343337616333393034353462313233613763313965373038666263393964
34633830663533623066643531363063663038656136376265346234376263373062653865393336
34663262323634303863353936346663663066643564663462383931373839343634366133623637
30353832303463396561383462333135303565373364613865656133623161623633633235313434
33343965313239383064313732363563306132643263343063333364333536363430666231636365
61306236393237333365393639663866633462376631396135363731636263323634663030663436
36323935333565366561376138386436636636353166643930306538396264316533323332306166
32386438353666363862343235376530666464306438336166393962643936316661663633623430
30646362643438636633643265386137313739336337396363663137656466303334623537663332
62623432643039313563393566323061393232663236666139316430646161393339316334663330
31333339393234316436393739633261333030663633623532363935396434343930353933633365
61343963366461626634373332663437653332343164636438393535336261393062343835356335
34393530363231383136623131396537396630393736396237333264336530663439633434333133
37363730376436643138626138383039383965383436393336663037326433393130623635313161
39383965316434383432326263646432333565363932653863666132646363623366346339373865
66363362643437663733353837623435393162633166336631306163656561326437303139656461
35616666386234326562663934316333336333616261343435363232613436346538376666353135
35326635346533643538643239386539353933613836623330613162363064633538663264376135
32306663303833336433623661633137623662613762393864333338623638313863633766366365
31613134633137336438313233356232333034373963323437363466363665653565303833366537
32396362306239633132363066303061643861313034306561363364343337313961306532346363
62653435306463663538633762623833356236366136636464323465386535623539313366386630
38343639623136323037363364393235316239643630313833353734346139663361616635666564
64343834373536316134303835653666383235656664633130366638343961363938663734396330
35663136636134643366356663646162306437646138316633346434386464323662363564636238
66313834303831376466346262353135333534323436303033623837303565633535326461376531
30303830383964306138656263336466383736373830666163646137663932643065343863643462
32366137656161343762393830316431366566653136663665396438653664333839323436323965
32616238636337633662643637326365316463373838363635613635663038313333633465356433
34366562303130623338316431626330386334393332373064633163383064383131376531343166
33373561663030626435393761363161373535643438653133643439386335343462613736343835
61656636356633373136393061663663316330653963333763386532363830363735306539326234
38613532396165336132303238613766646231646539343165373531366633643662633739386136
39373734323561633436633235313936646162313439323663643438393239383038643536353738
61373265373935626435636139303433643264663030306464303764373361363566336630333232
36626131343034316535303239306138306461653034663865656236376166663363666537363930
35376234313963646463623539343531383238316538616665386663306164616661
38313739326239353332383238666563376633336337393762323039653938323764653663366434
3231306135656132633339373761373735383333353539380a633963326265616661613532343334
65356162666462393733383631616437386263623833666239356262353835636161663936353735
6437383633383562350a373262646365646136333531623136643762646664326230643461323539
61663964643531623133393731663633663836306334336335613331363937373139623132396366
36663765376133666665373366666238376137303766366239323264393139326264373730633964
65316534386432353630313963353561663161616133303865316162316434366166663365373538
35616634656339333438353036343565633834343265386461613437643138633439623630366662
65343664376237633533643530306332396231656363303832396135346661656435333931623235
36323565616262626665653830643333323064653932646432373963616561386233313534643530
65393635353039343161356530633462646562623130323431303532383734613435326462643265
32643764653262343137356132656535636231633337306161393361663032623133303534316236
66303264393562366436393633313639303533366539333664346536623737616134323032366437
63316532333131666437303836356438666338333236316338613266373366356661636564653566
32383437656231646462353636303435383832613366393631646330626665393931393639363032
61666138636166346337646134613832646565336266313266663363643366636539386133616362
32656163363632643566616135326136393132393736623230666563356165336364353938663861
61316561323661336538383539356134393637653634343365306634363338343636356638373338
64363661656639396437653532623235616366393335633135666330393261663465396565393839
34343461663631633430356466383434653437616239663939323935336565326139393466313564
64396163386338663638306164336266383365373866633334636333663764353535666266633231
61303136613862653263613462303236323766386262366534653661623466623036323337353132
37323761323566383064616636386331393238336564363936633631396438613730666232623036
62613465316436666261393730636639366665643462316231663930663332646564663736313638
65353337613836366333333561306334333834376264373562333935363662663433336161303163
35363565346566646136326164653339653762643465653730383866353232643336343364353630
61393939626134613164343636336233663930393632616233336632306439363635376464343731
64626437363061393565396162316638663765303064326135306231653964626664353534313639
37306639623335653864643635666130373266623938383665366262636661333035663665363764
63363635633766646139636166326563663333376335393934313237613432646434373365633961
63316230386164616634653662653433346361393030386339383964313063643434326233363862
32383832366466663636626633393237333037303730616134623137366331643863313235626664
63653634656636323933346232356331353936376532633265633136336166653563656466616430
36643663663638666231353065343432343632303935303835623062333465326636636162316363
31623061343633306538316230663566366234386265363634383836363063393734616437353965
66306139363131663332363637353232393633306662353939666561343933313934373034376262
65636465643930616631613838643462643963363634323964666335346133633637393630356439
39356333373063663666313833393936376162646637343766383931386539373638643238313263
61626664656365323232643233326362343832343138353831313532353064306531636361383463
30636334383536316233336637386661393265396162613035393732313338363931333934653531
38653031393735623531656561353633623864383234376235343838643339306265653532356239
39346131633036373439343236323932353464633538666333323039626136326235333030656363
62366437353163333766646235633132346165313964323633633266613837393538623063393735
65613133303039343763

3
pi/setup.yml

@ -15,6 +15,7 @@
become: true
roles:
- role: k3s-control
# tags: [ control ]
- name: Provision agent nodes
hosts: agent
@ -22,4 +23,6 @@
become: true
roles:
- role: k3s-agents
# tags: [ agents ]

Loading…
Cancel
Save