ConfigLMM 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/Plugins/Apps/Authentik/Authentik-ProxyOutpost.container +14 -0
  4. data/Plugins/Apps/Authentik/Authentik-Server.container +1 -0
  5. data/Plugins/Apps/Authentik/Authentik-Worker.container +1 -0
  6. data/Plugins/Apps/Authentik/Authentik.conf.erb +7 -0
  7. data/Plugins/Apps/Authentik/Authentik.lmm.rb +24 -2
  8. data/Plugins/Apps/BookStack/BookStack.conf.erb +41 -0
  9. data/Plugins/Apps/BookStack/BookStack.container +15 -0
  10. data/Plugins/Apps/BookStack/BookStack.lmm.rb +80 -0
  11. data/Plugins/Apps/Discourse/Discourse-Sidekiq.container +17 -0
  12. data/Plugins/Apps/Discourse/Discourse.conf.erb +41 -0
  13. data/Plugins/Apps/Discourse/Discourse.container +17 -0
  14. data/Plugins/Apps/Discourse/Discourse.lmm.rb +95 -0
  15. data/Plugins/Apps/Dovecot/Dovecot.lmm.rb +25 -2
  16. data/Plugins/Apps/ERPNext/ERPNext-Frontend.container +19 -0
  17. data/Plugins/Apps/ERPNext/ERPNext-Queue.container +17 -0
  18. data/Plugins/Apps/ERPNext/ERPNext-Scheduler.container +17 -0
  19. data/Plugins/Apps/ERPNext/ERPNext-Websocket.container +19 -0
  20. data/Plugins/Apps/ERPNext/ERPNext.container +18 -0
  21. data/Plugins/Apps/ERPNext/ERPNext.lmm.rb +193 -0
  22. data/Plugins/Apps/ERPNext/ERPNext.network +12 -0
  23. data/Plugins/Apps/ERPNext/sites/apps.json +10 -0
  24. data/Plugins/Apps/ERPNext/sites/apps.txt +3 -0
  25. data/Plugins/Apps/ERPNext/sites/common_site_config.json +11 -0
  26. data/Plugins/Apps/GitLab/GitLab.container +3 -2
  27. data/Plugins/Apps/GitLab/GitLab.lmm.rb +37 -12
  28. data/Plugins/Apps/LetsEncrypt/LetsEncrypt.lmm.rb +57 -0
  29. data/Plugins/Apps/LetsEncrypt/hooks/dovecot.sh +2 -0
  30. data/Plugins/Apps/LetsEncrypt/hooks/nginx.sh +2 -0
  31. data/Plugins/Apps/LetsEncrypt/hooks/postfix.sh +2 -0
  32. data/Plugins/Apps/LetsEncrypt/renew-certificates.service +7 -0
  33. data/Plugins/Apps/LetsEncrypt/renew-certificates.timer +12 -0
  34. data/Plugins/Apps/LetsEncrypt/rfc2136.ini +11 -0
  35. data/Plugins/Apps/MariaDB/MariaDB.lmm.rb +115 -0
  36. data/Plugins/Apps/Matrix/Element.container +14 -0
  37. data/Plugins/Apps/Matrix/Matrix.conf.erb +49 -5
  38. data/Plugins/Apps/Matrix/Matrix.lmm.rb +86 -1
  39. data/Plugins/Apps/Matrix/Synapse.container +17 -0
  40. data/Plugins/Apps/Matrix/config.json +50 -0
  41. data/Plugins/Apps/Matrix/homeserver.yaml +70 -0
  42. data/Plugins/Apps/Matrix/log.config +30 -0
  43. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +70 -45
  44. data/Plugins/Apps/Nginx/conf.d/configlmm.conf +9 -0
  45. data/Plugins/Apps/Nginx/config-lmm/errors.conf +10 -4
  46. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +5 -1
  47. data/Plugins/Apps/Nginx/nginx.lmm.rb +15 -11
  48. data/Plugins/Apps/Nginx/proxy.conf.erb +13 -3
  49. data/Plugins/Apps/Odoo/Odoo.container +2 -1
  50. data/Plugins/Apps/Odoo/Odoo.lmm.rb +1 -1
  51. data/Plugins/Apps/OpenVidu/Ingress.container +18 -0
  52. data/Plugins/Apps/OpenVidu/OpenVidu.conf.erb +34 -0
  53. data/Plugins/Apps/OpenVidu/OpenVidu.container +16 -0
  54. data/Plugins/Apps/OpenVidu/OpenVidu.lmm.rb +90 -0
  55. data/Plugins/Apps/OpenVidu/OpenViduCall.conf.erb +35 -0
  56. data/Plugins/Apps/OpenVidu/OpenViduCall.container +15 -0
  57. data/Plugins/Apps/OpenVidu/ingress.yaml +10 -0
  58. data/Plugins/Apps/OpenVidu/livekit.yaml +13 -0
  59. data/Plugins/Apps/Peppermint/Peppermint.conf.erb +0 -4
  60. data/Plugins/Apps/Peppermint/Peppermint.container +2 -1
  61. data/Plugins/Apps/Postfix/Postfix.lmm.rb +32 -6
  62. data/Plugins/Apps/PostgreSQL/PostgreSQL.lmm.rb +80 -11
  63. data/Plugins/Apps/Roundcube/Roundcube.conf.erb +75 -0
  64. data/Plugins/Apps/Roundcube/Roundcube.lmm.rb +145 -0
  65. data/Plugins/Apps/Tunnel/tunnel.lmm.rb +63 -0
  66. data/Plugins/Apps/Tunnel/tunnelTCP.service +9 -0
  67. data/Plugins/Apps/Tunnel/tunnelTCP.socket +9 -0
  68. data/Plugins/Apps/Tunnel/tunnelUDP.service +9 -0
  69. data/Plugins/Apps/Tunnel/tunnelUDP.socket +9 -0
  70. data/Plugins/Apps/Valkey/Valkey.lmm.rb +32 -0
  71. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +4 -0
  72. data/Plugins/Apps/Wiki.js/Wiki.js.conf.erb +42 -0
  73. data/Plugins/Apps/Wiki.js/Wiki.js.container +15 -0
  74. data/Plugins/Apps/Wiki.js/Wiki.js.lmm.rb +61 -0
  75. data/Plugins/Apps/gollum/gollum.conf.erb +39 -1
  76. data/Plugins/Apps/gollum/gollum.container +4 -1
  77. data/Plugins/Apps/gollum/gollum.lmm.rb +11 -3
  78. data/Plugins/OS/Linux/Debian/preseed.cfg.erb +62 -0
  79. data/Plugins/OS/Linux/Distributions.yaml +32 -0
  80. data/Plugins/OS/Linux/Flavours.yaml +11 -0
  81. data/Plugins/OS/Linux/Linux.lmm.rb +255 -67
  82. data/Plugins/OS/Linux/Packages.yaml +47 -2
  83. data/Plugins/OS/Linux/Proxmox/answer.toml.erb +30 -0
  84. data/Plugins/OS/Linux/WireGuard/WireGuard.lmm.rb +33 -4
  85. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +1 -1
  86. data/Plugins/Platforms/libvirt/libvirt.lmm.rb +3 -2
  87. data/Plugins/Services/DNS/PowerDNS.lmm.rb +95 -8
  88. data/bootstrap.sh +41 -3
  89. data/lib/ConfigLMM/Framework/plugins/linuxApp.rb +146 -64
  90. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +34 -3
  91. data/lib/ConfigLMM/Framework/plugins/plugin.rb +62 -6
  92. data/lib/ConfigLMM/cli.rb +3 -1
  93. data/lib/ConfigLMM/commands/cleanup.rb +1 -0
  94. data/lib/ConfigLMM/commands/configsCommand.rb +3 -1
  95. data/lib/ConfigLMM/io/configList.rb +3 -1
  96. data/lib/ConfigLMM/state.rb +10 -2
  97. data/lib/ConfigLMM/version.rb +1 -1
  98. metadata +54 -3
  99. data/Plugins/Apps/GitLab/GitLab.conf.erb +0 -26
@@ -0,0 +1,193 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class ERPNext < Framework::NginxApp
5
+
6
+ USER = 'erpnext'
7
+ HOME_DIR = '/var/lib/erpnext'
8
+ VERSION = '15'
9
+ FRAPPE_REPO = 'https://github.com/frappe/frappe_docker.git'
10
+ IMAGE_ID = 'ConfigLM.moe/erpnext:v' + VERSION
11
+
12
+ def actionERPNextBuild(id, target, activeState, context, options)
13
+ buildContainer(id, target, options)
14
+ end
15
+
16
+ def buildContainer(id, target, options)
17
+ begin
18
+ Framework::LinuxApp.ensurePackage('git', '@me', 'git')
19
+ Framework::LinuxApp.ensurePackage('Podman', '@me', 'podman')
20
+ rescue RuntimeError => error
21
+ prompt.say(error, :color => :red)
22
+ end
23
+ frappe = File.expand_path(REPOS_CACHE + '/frappe_docker')
24
+ if !File.exist?(frappe)
25
+ mkdir(File.expand_path(REPOS_CACHE), false)
26
+ self.class.exec('cd #{REPOS_CACHE} && git clone --quiet #{FRAPPE_REPO}')
27
+ else
28
+ self.class.exec('cd #{REPOS_CACHE}/frappe_docker && git pull --quiet')
29
+ end
30
+ self.class.exec('cd #{REPOS_CACHE}/frappe_docker && git checkout . --quiet')
31
+
32
+ if !self.class.cmdSuccess?("podman image exists #{IMAGE_ID}")
33
+ appsJSON = Base64.urlsafe_encode64(File.read(__dir__ + '/sites/apps.json').gsub('$VERSION', VERSION))
34
+ self.class.exec("cd #{REPOS_CACHE}/frappe_docker && podman build --tag=#{IMAGE_ID} --build-arg APPS_JSON_BASE64=#{appsJSON} --build-arg FRAPPE_BRANCH=version-#{VERSION} --file images/custom/Containerfile .")
35
+ end
36
+ end
37
+
38
+ def actionERPNextDeploy(id, target, activeState, context, options)
39
+ raise Framework::PluginProcessError.new('Domain field must be set!') if (!target.key?('Proxy') || target['Proxy']) && !target['Domain']
40
+
41
+ target['Database'] ||= {}
42
+ if target['Location'] && target['Location'] != '@me'
43
+ uri = Addressable::URI.parse(target['Location'])
44
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
45
+
46
+ self.class.sshStart(uri) do |ssh|
47
+
48
+ activeState['Database'] = target['Database']
49
+ dbPassword = self.configureMariaDB(target['Database'], activeState, ssh)
50
+ distroInfo = Framework::LinuxApp.currentDistroInfo(ssh)
51
+ Framework::LinuxApp.configurePodmanServiceOverSSH(USER, HOME_DIR, 'ERPNext', distroInfo, ssh)
52
+ self.class.exec("su --login #{USER} --shell /bin/sh --command 'mkdir -p ~/sites ~/logs'", ssh)
53
+
54
+ cmd = self.class.cmdSSH(uri)
55
+ self.class.exec("podman image save ConfigLM.moe/erpnext:v#{VERSION} | #{cmd} 'cat > #{HOME_DIR}/erpnext.tar'")
56
+ self.class.exec("su --login #{USER} --shell /usr/bin/sh --command 'podman image load --input erpnext.tar'", ssh)
57
+ self.class.exec("rm -f #{HOME_DIR}/erpnext.tar", ssh)
58
+
59
+ path = Framework::LinuxApp::SYSTEMD_CONTAINERS_PATH.gsub('~', HOME_DIR)
60
+ self.class.exec(" echo 'FRAPPE_DB_PASSWORD=#{dbPassword}' > #{path}/ERPNext.env", ssh)
61
+ self.class.exec("echo 'FRAPPE_SITE_NAME_HEADER=erpnext' >> #{path}/ERPNext.env", ssh)
62
+ #self.class.exec("echo 'UPSTREAM_REAL_IP_ADDRESS=127.0.0.1' >> #{path}/ERPNext.env", ssh)
63
+ #self.class.exec("echo 'UPSTREAM_REAL_IP_RECURSIVE=on' >> #{path}/ERPNext.env", ssh)
64
+ self.class.exec("echo 'BACKEND=10.90.50.10:8000' >> #{path}/ERPNext.env", ssh)
65
+ self.class.exec("echo 'SOCKETIO=10.90.50.11:9000' >> #{path}/ERPNext.env", ssh)
66
+
67
+ self.class.exec("chown #{USER}:#{USER} #{path}/ERPNext.env", ssh)
68
+ self.class.exec("chmod 600 #{path}/ERPNext.env", ssh)
69
+
70
+ ssh.scp.upload!(__dir__ + '/sites/apps.txt', HOME_DIR + '/sites/')
71
+ ssh.scp.upload!(__dir__ + '/sites/common_site_config.json', HOME_DIR + '/sites/')
72
+
73
+ if target['Database'] && target['Database']['HostName']
74
+ self.class.exec("sed -i 's|\"10.0.2.2\"|\"#{target['Database']['HostName']}\"|' #{HOME_DIR}/sites/common_site_config.json", ssh)
75
+ end
76
+
77
+ if target['Valkey']
78
+ self.class.exec("sed -i 's|10.0.2.2:6379|#{target['Valkey']}|' #{HOME_DIR}/sites/common_site_config.json", ssh)
79
+ end
80
+
81
+ valkeyPassword = ENV[id + '-VALKEY_PASSWORD'] || ENV['VALKEY_PASSWORD']
82
+ if valkeyPassword
83
+ self.class.exec("sed -i 's|\"use_rq_auth\": false|\"use_rq_auth\": true|' #{HOME_DIR}/sites/common_site_config.json", ssh)
84
+ self.class.exec("sed -i 's|$VALKEY_PASSWORD|#{valkeyPassword}|' #{HOME_DIR}/sites/common_site_config.json", ssh)
85
+ end
86
+
87
+ self.class.exec("chown -R #{USER}:#{USER} " + HOME_DIR + '/sites', ssh)
88
+
89
+ ssh.scp.upload!(__dir__ + '/ERPNext.network', path)
90
+ ssh.scp.upload!(__dir__ + '/ERPNext.container', path)
91
+ ssh.scp.upload!(__dir__ + '/ERPNext-Queue.container', path)
92
+ ssh.scp.upload!(__dir__ + '/ERPNext-Scheduler.container', path)
93
+ ssh.scp.upload!(__dir__ + '/ERPNext-Websocket.container', path)
94
+ ssh.scp.upload!(__dir__ + '/ERPNext-Frontend.container', path)
95
+ self.class.exec("sed -i 's|$VERSION|#{VERSION}|' #{path}/ERPNext.container", ssh)
96
+ self.class.exec("sed -i 's|$VERSION|#{VERSION}|' #{path}/ERPNext-Queue.container", ssh)
97
+ self.class.exec("sed -i 's|$VERSION|#{VERSION}|' #{path}/ERPNext-Scheduler.container", ssh)
98
+ self.class.exec("sed -i 's|$VERSION|#{VERSION}|' #{path}/ERPNext-Websocket.container", ssh)
99
+ self.class.exec("sed -i 's|$VERSION|#{VERSION}|' #{path}/ERPNext-Frontend.container", ssh)
100
+
101
+ if !target.key?('Proxy') || target['Proxy']
102
+ deployNginxProxyConfig('http://127.0.0.1:18400', 'ERPNext', id, target, activeState, state, context, options, ssh)
103
+ elsif target.key?('Proxy') && target['Proxy'] == false
104
+ self.class.exec("sed -i 's|PublishPort=127.0.0.1:18400:|PublishPort=0.0.0.0:18400:|' #{path}ERPNext-Frontend.container", ssh)
105
+ Framework::LinuxApp.firewallAddPort('18400/tcp', ssh)
106
+ end
107
+
108
+ self.class.exec("systemctl --user --machine=#{USER}@ daemon-reload", ssh)
109
+ self.class.exec("systemctl --user --machine=#{USER}@ restart ERPNext-network", ssh)
110
+ self.class.exec("systemctl --user --machine=#{USER}@ restart ERPNext", ssh)
111
+
112
+ containers = JSON.parse(self.class.exec("su --login #{USER} --shell /usr/bin/sh --command 'podman ps --format json --filter name=^ERPNext$'", ssh).strip)
113
+ raise 'Failed to find container!' if containers.empty?
114
+
115
+ MariaDB.executeRemotely(target['Database'], ssh) do |sshDB|
116
+ if !MariaDB.tableExist?(USER, 'tabUser', sshDB)
117
+ adminPassword = SecureRandom.alphanumeric(20)
118
+ self.class.exec("rm -rf " + HOME_DIR + '/sites/erpnext', ssh)
119
+ #self.class.exec(" su --login #{USER} --shell /usr/bin/sh --command \"podman exec #{containers.first['Id']} sh -c 'bench new-site --no-setup-db --db-name erpnext --db-user erpnext --admin-password #{adminPassword} --install-app erpnext --set-default erpnext'\"", ssh)
120
+ dbAdminPassword = MariaDB.createAdmin(sshDB)
121
+ MariaDB.executeSQL("DROP DATABASE #{USER}", nil, sshDB)
122
+ self.class.exec(" su --login #{USER} --shell /usr/bin/sh --command \" podman exec #{containers.first['Id']} sh -c ' bench new-site --db-root-username admin --db-root-password #{dbAdminPassword} --db-name erpnext --admin-password #{adminPassword} --install-app erpnext --set-default erpnext'\"", ssh)
123
+ MariaDB.dropAdmin(sshDB)
124
+ self.class.exec("su --login #{USER} --shell /usr/bin/sh --command \"podman exec #{containers.first['Id']} sh -c 'bench --site erpnext install-app hrms'\"", ssh)
125
+ prompt.say("Administrator password: #{adminPassword}", :color => :magenta)
126
+ end
127
+ end
128
+
129
+ self.class.exec("systemctl --user --machine=#{USER}@ restart ERPNext-Queue", ssh)
130
+ self.class.exec("systemctl --user --machine=#{USER}@ restart ERPNext-Scheduler", ssh)
131
+ self.class.exec("systemctl --user --machine=#{USER}@ restart ERPNext-Websocket", ssh)
132
+ self.class.exec("systemctl --user --machine=#{USER}@ restart ERPNext-Frontend", ssh)
133
+
134
+
135
+ end
136
+ else
137
+ # TODO
138
+ end
139
+ end
140
+
141
+ def configureMariaDB(settings, activeState, ssh)
142
+ password = SecureRandom.alphanumeric(20)
143
+ MariaDB.createRemoteUserAndDB(settings, USER, password, ssh)
144
+ password
145
+ end
146
+
147
+ def cleanup(configs, state, context, options)
148
+ cleanupType(:ERPNext, configs, state, context, options) do |item, id, state, context, options, ssh|
149
+ if item['Proxy'].nil? || item['Proxy']
150
+ self.cleanupNginxConfig('ERPNext', id, state, context, options, ssh)
151
+ self.class.reload(ssh, options[:dry])
152
+ end
153
+ Framework::LinuxApp.firewallRemovePort('18400/tcp', ssh, options[:dry])
154
+
155
+ self.class.exec("systemctl --user --machine=#{USER}@ stop ERPNext", ssh, true, options[:dry])
156
+ self.class.exec("systemctl --user --machine=#{USER}@ stop ERPNext-Frontend", ssh, true, options[:dry])
157
+ self.class.exec("systemctl --user --machine=#{USER}@ stop ERPNext-Websocket", ssh, true, options[:dry])
158
+ self.class.exec("systemctl --user --machine=#{USER}@ stop ERPNext-Scheduler", ssh, true, options[:dry])
159
+ self.class.exec("systemctl --user --machine=#{USER}@ stop ERPNext-Queue", ssh, true, options[:dry])
160
+ self.class.exec("systemctl --user --machine=#{USER}@ stop ERPNext-network", ssh, true, options[:dry])
161
+
162
+ path = Framework::LinuxApp::SYSTEMD_CONTAINERS_PATH.gsub('~', HOME_DIR)
163
+ rm(path + 'ERPNext.network', options[:dry], ssh)
164
+ rm(path + 'ERPNext.container', options[:dry], ssh)
165
+ rm(path + 'ERPNext-Queue.container', options[:dry], ssh)
166
+ rm(path + 'ERPNext-Scheduler.container', options[:dry], ssh)
167
+ rm(path + 'ERPNext-Websocket.container', options[:dry], ssh)
168
+ rm(path + 'ERPNext-Frontend.container', options[:dry], ssh)
169
+
170
+ self.class.exec("podman rmi #{IMAGE_ID}", ssh, true, options[:dry])
171
+
172
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
173
+
174
+ if options[:destroy]
175
+ item['Database'] ||= {}
176
+ MariaDB.executeRemotely(item['Database'], ssh) do |sshDB|
177
+ MariaDB.executeSQL("DROP DATABASE #{USER}", nil, sshDB, true, options[:dry])
178
+ end
179
+ Framework::LinuxApp.deleteUserAndGroup(USER, ssh, options[:dry])
180
+ rm(HOME_DIR, options[:dry], ssh)
181
+ rm('/var/log/nginx/erpnext.access.log', options[:dry], ssh)
182
+ rm('/var/log/nginx/erpnext.error.log', options[:dry], ssh)
183
+
184
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+ end
192
+
193
+
@@ -0,0 +1,12 @@
1
+
2
+ [Unit]
3
+ Description=ERPNext network
4
+ After=network-online.target
5
+
6
+ [Network]
7
+ NetworkName=ERPNext
8
+ Subnet=10.90.50.0/28
9
+ Gateway=10.90.50.1
10
+
11
+ [Install]
12
+ WantedBy=default.target
@@ -0,0 +1,10 @@
1
+ [
2
+ {
3
+ "url": "https://github.com/frappe/erpnext",
4
+ "branch": "version-$VERSION"
5
+ },
6
+ {
7
+ "url": "https://github.com/frappe/hrms",
8
+ "branch": "version-$VERSION"
9
+ }
10
+ ]
@@ -0,0 +1,3 @@
1
+ erpnext
2
+ frappe
3
+ hrms
@@ -0,0 +1,11 @@
1
+ {
2
+ "db_host": "10.0.2.2",
3
+ "db_port": 3306,
4
+ "redis_cache": "redis://default:$VALKEY_PASSWORD@10.0.2.2:6379",
5
+ "redis_queue": "redis://default:$VALKEY_PASSWORD@10.0.2.2:6379",
6
+ "redis_socketio": "redis://default:$VALKEY_PASSWORD@10.0.2.2:6379",
7
+ "use_rq_auth": false,
8
+ "rq_password": "$VALKEY_PASSWORD",
9
+ "rq_username": "default",
10
+ "socketio_port": 9000
11
+ }
@@ -1,10 +1,10 @@
1
1
 
2
2
  [Unit]
3
3
  Description=GitLab container
4
- After=local-fs.target
4
+ After=local-fs.target firewalld.service
5
5
 
6
6
  [Container]
7
- Image=gitlab/gitlab-ce:nightly
7
+ Image=docker.io/gitlab/gitlab-ce:latest
8
8
  PublishPort=127.0.0.1:18100:80
9
9
  PublishPort=0.0.0.0:22:22
10
10
  Volume=/var/lib/gitlab/config:/etc/gitlab
@@ -12,6 +12,7 @@ Volume=/var/lib/gitlab/logs:/var/log/gitlab
12
12
  Volume=/var/lib/gitlab/data:/var/opt/gitlab
13
13
  Volume=/var/lib/gitlab/backups:/var/opt/gitlab/backups
14
14
  ShmSize=256M
15
+ AutoUpdate=registry
15
16
 
16
17
  [Install]
17
18
  WantedBy=multi-user.target default.target
@@ -4,6 +4,7 @@ module ConfigLMM
4
4
  class GitLab < Framework::NginxApp
5
5
 
6
6
  HOME_DIR = '/var/lib/gitlab'
7
+ IMAGE_ID = 'docker.io/gitlab/gitlab-ce:latest'
7
8
 
8
9
  def actionGitLabDeploy(id, target, activeState, context, options)
9
10
 
@@ -15,22 +16,25 @@ module ConfigLMM
15
16
  self.prepareConfig(target, ssh)
16
17
 
17
18
  distroInfo = Framework::LinuxApp.currentDistroInfo(ssh)
18
- self.class.sshExec!(ssh, "mkdir -p #{HOME_DIR}/config")
19
- self.class.sshExec!(ssh, "mkdir -p #{HOME_DIR}/logs")
20
- self.class.sshExec!(ssh, "mkdir -p #{HOME_DIR}/data")
21
- self.class.sshExec!(ssh, "mkdir -p #{HOME_DIR}/backups")
19
+ self.class.exec("mkdir -p #{HOME_DIR}/config", ssh)
20
+ self.class.exec("mkdir -p #{HOME_DIR}/logs", ssh)
21
+ self.class.exec("mkdir -p #{HOME_DIR}/data", ssh)
22
+ self.class.exec("mkdir -p #{HOME_DIR}/backups", ssh)
22
23
 
23
24
  path = '/etc/containers/systemd'
24
25
  ssh.scp.upload!(__dir__ + '/GitLab.container', path)
25
- self.class.sshExec!(ssh, "systemctl daemon-reload")
26
- self.class.sshExec!(ssh, "systemctl start GitLab")
27
26
 
28
- Framework::LinuxApp.ensureServiceAutoStartOverSSH(NGINX_PACKAGE, ssh)
29
- self.writeNginxConfig(__dir__, 'GitLab', id, target, state, context, options)
30
- self.deployNginxConfig(id, target, activeState, context, options)
31
- Framework::LinuxApp.startServiceOverSSH(NGINX_PACKAGE, ssh)
27
+ if !target.key?('Proxy') || target['Proxy']
28
+ deployNginxProxyConfig('http://127.0.0.1:18100', 'GitLab', id, target, activeState, state, context, options, ssh)
29
+ elsif target.key?('Proxy') && target['Proxy'] == false
30
+ self.class.exec("sed -i 's|PublishPort=127.0.0.1:18100:|PublishPort=0.0.0.0:18100:|' #{path}/GitLab.container", ssh)
31
+ Framework::LinuxApp.firewallAddPort('18100/tcp', ssh)
32
+ end
33
+
34
+ Framework::LinuxApp.reloadServiceManager(ssh)
35
+ Framework::LinuxApp.restartService('GitLab', ssh)
32
36
 
33
- configFile = '/var/lib/gitlab/config/gitlab.rb'
37
+ configFile = HOME_DIR + '/config/gitlab.rb'
34
38
  while !self.class.remoteFilePresent?(configFile, ssh)
35
39
  sleep(2)
36
40
  end
@@ -54,11 +58,12 @@ module ConfigLMM
54
58
  end
55
59
  end
56
60
 
57
- self.class.sshExec!(ssh, "systemctl restart GitLab")
61
+ Framework::LinuxApp.restartService('GitLab', ssh)
58
62
  end
59
63
  else
60
64
  # TODO
61
65
  end
66
+ activeState['Status'] = State::STATUS_DEPLOYED
62
67
  end
63
68
 
64
69
  def prepareConfig(target, ssh)
@@ -68,6 +73,26 @@ module ConfigLMM
68
73
  self.class.prepareNginxConfig(target, ssh)
69
74
  end
70
75
 
76
+ def cleanup(configs, state, context, options)
77
+ cleanupType(:GitLab, configs, state, context, options) do |item, id, state, context, options, ssh|
78
+ if item['Proxy'].nil? || item['Proxy']
79
+ self.cleanupNginxConfig('GitLab', id, state, context, options, ssh)
80
+ self.class.reload(ssh, options[:dry])
81
+ end
82
+ Framework::LinuxApp.firewallRemovePort('18100/tcp', ssh, options[:dry])
83
+ Framework::LinuxApp.stopService('GitLab', ssh, options[:dry])
84
+ rm('/etc/containers/systemd/GitLab.container', options[:dry], ssh)
85
+ self.class.exec("podman rmi #{IMAGE_ID}", ssh, true, options[:dry])
86
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
87
+ if options[:destroy]
88
+ rm('/var/lib/gitlab', options[:dry], ssh)
89
+ rm('/var/log/nginx/gitlab.access.log', options[:dry], ssh)
90
+ rm('/var/log/nginx/gitlab.error.log', options[:dry], ssh)
91
+ state.item(id)['Status'] = State::STATUS_DESTROYED unless options[:dry]
92
+ end
93
+ end
94
+ end
95
+
71
96
  end
72
97
 
73
98
  end
@@ -0,0 +1,57 @@
1
+
2
+ module ConfigLMM
3
+ module LMM
4
+ class LetsEncrypt < Framework::LinuxApp
5
+
6
+ PACKAGE_NAME = 'CertBotNginx'
7
+ CONFIG_DIR = '/etc/letsencrypt/'
8
+
9
+ def actionLetsEncryptDeploy(id, target, activeState, context, options)
10
+ self.ensurePackage(PACKAGE_NAME, target['Location'])
11
+
12
+ if target['Location'] && target['Location'] != '@me'
13
+ uri = Addressable::URI.parse(target['Location'])
14
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
15
+
16
+ self.class.sshStart(uri) do |ssh|
17
+ ssh.scp.upload!(__dir__ + '/rfc2136.ini', CONFIG_DIR)
18
+ ssh.scp.upload!(__dir__ + '/renew-certificates.service', '/etc/systemd/system/')
19
+ ssh.scp.upload!(__dir__ + '/renew-certificates.timer', '/etc/systemd/system/')
20
+ self.class.exec("mkdir -p #{CONFIG_DIR}renewal-hooks/deploy", ssh)
21
+ target['Hooks'].to_a.each do |hook|
22
+ ssh.scp.upload!(__dir__ + '/hooks/' + hook + '.sh', "#{CONFIG_DIR}renewal-hooks/deploy/")
23
+ end
24
+ self.class.exec("chmod +x #{CONFIG_DIR}renewal-hooks/deploy/*.sh", ssh)
25
+ self.class.exec("sed -i 's|$IP|#{target['DNS']['IP']}|' #{CONFIG_DIR}/rfc2136.ini", ssh)
26
+ self.class.exec("sed -i 's|$SECRET|#{ENV['LETSENCRYPT_DNS_SECRET']}|' #{CONFIG_DIR}/rfc2136.ini", ssh)
27
+ self.class.exec("chmod 600 #{CONFIG_DIR}/rfc2136.ini", ssh)
28
+ if target['Domain']
29
+ createCertificate('Wildcard', target['Domain'], target, ssh)
30
+ end
31
+ target['Certificates'].to_h.each do |name, domain|
32
+ createCertificate(name, domain, target, ssh)
33
+ end
34
+
35
+ self.class.exec("systemctl daemon-reload", ssh)
36
+ self.class.exec("systemctl enable renew-certificates.timer", ssh)
37
+ self.class.exec("systemctl start renew-certificates.timer", ssh)
38
+ end
39
+ else
40
+ # TODO
41
+ end
42
+ end
43
+
44
+ def createCertificate(name, domain, target, ssh)
45
+ domains = ['--domains "' + Addressable::IDNA.to_ascii(domain) + '"']
46
+ if domain.start_with?('*.')
47
+ domains << '--domains "' + Addressable::IDNA.to_ascii(domain[2..-1]) + '"'
48
+ end
49
+ extra = ''
50
+ extra = '--dns-rfc2136-propagation-seconds ' + target['DNS']['Propagation'].to_s if target['DNS']['Propagation']
51
+ self.class.exec("certbot certonly --dns-rfc2136 --dns-rfc2136-credentials=/etc/letsencrypt/rfc2136.ini #{extra} --non-interactive --agree-tos --email #{target['EMail']} --cert-name '#{name}' #{domains.join(' ')}", ssh)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/sh
2
+ systemctl reload dovecot
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/sh
2
+ systemctl reload nginx
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/sh
2
+ systemctl reload postfix
@@ -0,0 +1,7 @@
1
+
2
+ [Unit]
3
+ Description=Renew certificates
4
+
5
+ [Service]
6
+ Type=oneshot
7
+ ExecStart=/usr/bin/certbot renew
@@ -0,0 +1,12 @@
1
+
2
+ [Unit]
3
+ Description=Renew certificates
4
+
5
+ [Timer]
6
+ OnCalendar=*-*-1/2 04:20
7
+ AccuracySec=12h
8
+ RandomizedDelaySec=24h
9
+ Persistent=true
10
+
11
+ [Install]
12
+ WantedBy=timers.target
@@ -0,0 +1,11 @@
1
+
2
+ # Target DNS server
3
+ dns_rfc2136_server = $IP
4
+ # Target DNS port
5
+ dns_rfc2136_port = 53
6
+ # TSIG key name
7
+ dns_rfc2136_name = configlmm.
8
+ # TSIG key secret
9
+ dns_rfc2136_secret = $SECRET
10
+ # TSIG key algorithm
11
+ dns_rfc2136_algorithm = HMAC-SHA512
@@ -0,0 +1,115 @@
1
+ require_relative '../../OS/Linux/Linux.lmm.rb'
2
+
3
+ module ConfigLMM
4
+ module LMM
5
+ class MariaDB < Framework::LinuxApp
6
+ PACKAGE_NAME = 'MariaDB'
7
+ SERVICE_NAME = 'mariadb'
8
+ USER_NAME = 'mariadb'
9
+
10
+ def actionMariaDBDeploy(id, target, activeState, context, options)
11
+ self.ensurePackage(PACKAGE_NAME, target['Location'])
12
+ self.ensureServiceAutoStart(SERVICE_NAME, target['Location'])
13
+ self.startService(SERVICE_NAME, target['Location'])
14
+
15
+ if target['Location'] && target['Location'] != '@me'
16
+ uri = Addressable::URI.parse(target['Location'])
17
+ raise Framework::PluginProcessError.new("#{id}: Unknown Protocol: #{uri.scheme}!") if uri.scheme != 'ssh'
18
+
19
+ self.class.sshStart(uri) do |ssh|
20
+ self.class.secureInstallation(ssh)
21
+ if target['Listen']
22
+ self.class.exec("sed -i 's|bind-address .*|bind-address = #{target['Listen']}|' /etc/my.cnf", ssh)
23
+ self.class.restartService(SERVICE_NAME, ssh)
24
+ end
25
+ end
26
+ else
27
+ # TODO
28
+ end
29
+
30
+ activeState['Status'] = State::STATUS_DEPLOYED
31
+ end
32
+
33
+ def cleanup(configs, state, context, options)
34
+ cleanupType(:MariaDB, configs, state, context, options) do |item, id, state, context, options, ssh|
35
+ Framework::LinuxApp.stopService(SERVICE_NAME, ssh, options[:dry])
36
+ Framework::LinuxApp.disableService(SERVICE_NAME, ssh, options[:dry])
37
+ Framework::LinuxApp.removePackage(PACKAGE_NAME, ssh, options[:dry])
38
+
39
+ state.item(id)['Status'] = State::STATUS_DELETED unless options[:dry]
40
+ end
41
+ end
42
+
43
+ def self.secureInstallation(ssh)
44
+ status = {}
45
+ output = ''
46
+ channel = ssh.exec("mariadb-secure-installation", status: status) do |channel, stream, data|
47
+ output += data
48
+ channel.send_data("\n") # Empty root password
49
+ channel.send_data("Y\n") # unix_socket authentication
50
+ channel.send_data("N\n") # change the root password
51
+ channel.send_data("Y\n") # remove anonymous users
52
+ channel.send_data("Y\n") # disallow root login remotely
53
+ channel.send_data("Y\n") # remove test database
54
+ channel.send_data("Y\n") # reload privileges
55
+ end
56
+ channel.wait
57
+ if !status[:exit_code].zero?
58
+ $stderr.puts(output)
59
+ raise Framework::PluginProcessError.new("mariadb-secure-installation failed!")
60
+ end
61
+ end
62
+
63
+ def self.createRemoteUserAndDB(settings, user, password, ssh = nil)
64
+ self.executeRemotely(settings, ssh) do |ssh|
65
+ host = 'localhost'
66
+ host = '%' if settings['HostName'] != 'localhost'
67
+ self.createUserAndDB(user, password, host, ssh)
68
+ end
69
+ end
70
+
71
+ def self.executeRemotely(settings, ssh = nil)
72
+ settings['HostName'] = 'localhost' unless settings['HostName']
73
+ if settings['HostName'] == 'localhost'
74
+ yield(ssh)
75
+ else
76
+ self.sshStart("ssh://#{settings['HostName']}/") do |ssh|
77
+ yield(ssh)
78
+ end
79
+ end
80
+ end
81
+
82
+ def self.createUserAndDB(user, password, host, ssh = nil)
83
+ self.executeSQL("CREATE USER '#{user}'@'#{host}'", nil, ssh, true)
84
+ self.executeSQL("ALTER USER '#{user}'@'#{host}' IDENTIFIED BY '#{password}'", nil, ssh)
85
+ self.executeSQL("CREATE DATABASE #{user}", nil, ssh, true)
86
+ self.executeSQL("GRANT ALL PRIVILEGES ON #{user}.* TO '#{user}'@'#{host}'", nil, ssh)
87
+ end
88
+
89
+ def self.createAdmin(ssh)
90
+ self.executeSQL("CREATE USER 'admin'@'%'", nil, ssh, true)
91
+ password = SecureRandom.alphanumeric(20)
92
+ self.executeSQL("ALTER USER 'admin'@'%' IDENTIFIED BY '#{password}'", nil, ssh)
93
+ self.executeSQL("GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION", nil, ssh)
94
+ password
95
+ end
96
+
97
+ def self.dropAdmin(ssh)
98
+ self.executeSQL("DROP USER 'admin'@'%'", nil, ssh, true)
99
+ end
100
+
101
+ def self.tableExist?(db, table, ssh)
102
+ table = self.executeSQL("SHOW TABLES LIKE '#{table}'", db, ssh).strip
103
+ !table.empty?
104
+ end
105
+
106
+ def self.executeSQL(sql, db = nil, ssh = nil, allowFailure = false, dry = false)
107
+ db = '' unless db
108
+ cmd = " mariadb #{db} --execute=\"#{sql.gsub('"', '\\"')};\""
109
+ self.exec(cmd, ssh, allowFailure, dry)
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,14 @@
1
+
2
+ [Unit]
3
+ Description=Matrix (Element) container
4
+ After=local-fs.target
5
+
6
+ [Container]
7
+ Image=docker.io/vectorim/element-web:latest
8
+ EnvironmentFile=/var/lib/matrix/.config/containers/systemd/Matrix.env
9
+ PublishPort=127.0.0.1:18300:80
10
+ Volume=/var/lib/matrix/config.json:/app/config.json
11
+ AutoUpdate=registry
12
+
13
+ [Install]
14
+ WantedBy=multi-user.target default.target