deploymatic 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 12555f536cf8666ea1775d6aae612836b7276c6a0c32f302c7fd965b3809178e
4
+ data.tar.gz: bf088645b074786b553768566c826d6d1d6a960ffa6055d5cb8ab6b34c8448b7
5
+ SHA512:
6
+ metadata.gz: 6a00806c4301a5b163bd55113ab376fb31c91d63689648f4019a869921c6c456d617bb73720efecc2d6561a5f00881cdf27e6afde4d46597d827b3fbfc8d7aef
7
+ data.tar.gz: dfcaf5c2a4404f26c5a45465565693916fd8c1b3752fa4e5f489e9f4a6ea3c55736386572f4f8e7bacc8086c7c1c1af81350139f278b5e2d2651ce12a211b0de
data/.rubocop.yml ADDED
@@ -0,0 +1,167 @@
1
+ require:
2
+ - rubocop-minitest
3
+ - rubocop-performance
4
+ - rubocop-rake
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 3.2.0
8
+ NewCops: enable
9
+
10
+ Layout/ArgumentAlignment:
11
+ EnforcedStyle: with_fixed_indentation
12
+
13
+ Layout/DotPosition:
14
+ EnforcedStyle: trailing
15
+
16
+ Layout/FirstArrayElementIndentation:
17
+ EnforcedStyle: consistent
18
+
19
+ Layout/FirstHashElementIndentation:
20
+ EnforcedStyle: consistent
21
+
22
+ Layout/HashAlignment:
23
+ EnforcedHashRocketStyle: table
24
+
25
+ # Layout/IndentationConsistency:
26
+ # EnforcedStyle: indented_internal_methods
27
+
28
+ Layout/LineLength:
29
+ Enabled: false
30
+ Max: 120
31
+
32
+ Layout/MultilineMethodCallIndentation:
33
+ EnforcedStyle: indented
34
+
35
+ Layout/MultilineOperationIndentation:
36
+ EnforcedStyle: indented
37
+
38
+ Layout/ParameterAlignment:
39
+ EnforcedStyle: with_fixed_indentation
40
+
41
+ Metrics/AbcSize:
42
+ Enabled: false
43
+
44
+ Metrics/BlockLength:
45
+ Max: 100
46
+ Exclude:
47
+ - test/**/*.rb
48
+
49
+ Metrics/BlockNesting:
50
+ Max: 3
51
+
52
+ Metrics/ClassLength:
53
+ Enabled: false
54
+
55
+ Metrics/CyclomaticComplexity:
56
+ Max: 25
57
+
58
+ Metrics/MethodLength:
59
+ Max: 100
60
+ Exclude:
61
+ - test/**/*.rb
62
+
63
+ Metrics/ModuleLength:
64
+ Enabled: false
65
+
66
+ Metrics/ParameterLists:
67
+ CountKeywordArgs: false
68
+ Exclude:
69
+ - test/**/*.rb
70
+
71
+ Metrics/PerceivedComplexity:
72
+ Max: 25
73
+
74
+ Minitest/AssertInDelta:
75
+ Enabled: false
76
+
77
+ Naming/VariableNumber:
78
+ Enabled: false
79
+
80
+ Performance/Casecmp:
81
+ Enabled: false
82
+
83
+ Style/AsciiComments:
84
+ Enabled: false
85
+
86
+ # Style/BlockDelimiters:
87
+ # Enabled: false
88
+
89
+ Style/ClassVars:
90
+ Enabled: false
91
+
92
+ # Style/CombinableLoops:
93
+ # Enabled: false
94
+
95
+ # Style/ConditionalAssignment:
96
+ # Enabled: false
97
+
98
+ Style/Documentation:
99
+ Enabled: false
100
+
101
+ Style/DocumentationMethod:
102
+ Enabled: false
103
+ Exclude:
104
+ - test/**/*
105
+
106
+ Style/EmptyMethod:
107
+ EnforcedStyle: expanded
108
+
109
+ Style/FormatStringToken:
110
+ EnforcedStyle: template
111
+
112
+ Style/FrozenStringLiteralComment:
113
+ Enabled: false
114
+
115
+ # Style/GuardClause:
116
+ # Enabled: false
117
+
118
+ Style/HashSyntax:
119
+ EnforcedShorthandSyntax: either
120
+
121
+ # Style/IfUnlessModifier:
122
+ # Enabled: false
123
+
124
+ Style/Lambda:
125
+ EnforcedStyle: literal
126
+
127
+ # Style/MethodDefParentheses:
128
+ # Enabled: false
129
+
130
+ # Style/NegatedIf:
131
+ # Enabled: false
132
+
133
+ # Style/Next:
134
+ # Enabled: false
135
+
136
+ # Style/NumericPredicate:
137
+ # Enabled: false
138
+
139
+ # Style/OpenStructUse:
140
+ # Enabled: false
141
+
142
+ Style/RaiseArgs:
143
+ EnforcedStyle: compact
144
+
145
+ Style/RedundantReturn:
146
+ Enabled: false
147
+
148
+ Style/RedundantSelf:
149
+ Enabled: false
150
+
151
+ Style/RegexpLiteral:
152
+ AllowInnerSlashes: true
153
+
154
+ # Style/RescueModifier:
155
+ # Enabled: false
156
+
157
+ # Style/SafeNavigation:
158
+ # Enabled: true
159
+
160
+ Style/SingleLineMethods:
161
+ Enabled: false
162
+
163
+ Style/StringConcatenation:
164
+ Mode: conservative
165
+
166
+ # Style/SymbolArray:
167
+ # Enabled: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 pioz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Deploymatic
2
+
3
+ Deploymatic is a Ruby tool for deploying services on a remote host using Git and systemd. It automates the installation, deployment, and management of services, making it easier to maintain and operate your applications.
4
+
5
+ ## Installation
6
+
7
+ Clone the repository and install the required gems:
8
+
9
+ ```bash
10
+ gem install deploymatic
11
+ ```
12
+
13
+ ## Configuration
14
+
15
+ Create a configuration file `.matic.yml` in the root of your project with the following structure:
16
+
17
+ ```yaml
18
+ name: meteo_station_example
19
+ ssh_user: pioz
20
+ ssh_host: 192.168.0.243
21
+ ssh_port: 22 # Optional, default 22
22
+ repo: https://github.com/pioz/meteo_station_example.git
23
+ install_dir: /home/pioz/meteo_station_example # Optional, default $HOME/<service_name>
24
+ install_commands: # Optional
25
+ - bundle install
26
+ - rake db:migrate
27
+ start_command: bundle exec puma -C config/puma.rb
28
+ stop_command: pkill -f puma # Optional
29
+ log_path: /path/to/log/file # Optional
30
+ run_after: network-online.target # Optional
31
+ start_limit_burst: 5 # Optional
32
+ start_limit_interval_seconds: 10 # Optional
33
+ environment_variables: # Optional
34
+ - RAILS_ENV=production
35
+ - SECRET_KEY_BASE=your_secret_key
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ Use `deploymatic` to manage your service. The available commands are:
41
+
42
+ - **install**: Install the service on the remote host
43
+ - **uninstall**: Uninstall the service
44
+ - **deploy**: Deploy the latest version of the service
45
+ - **start**: Start the service
46
+ - **stop**: Stop the service
47
+ - **restart**: Restart the service
48
+ - **status**: Show the current status of the service
49
+ - **show**: Display the systemd unit service file of the service
50
+
51
+ ### Example
52
+
53
+ ```bash
54
+ deploymatic install
55
+ deploymatic start
56
+ ```
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it (https://github.com/pioz/deploymatic/fork)
61
+ 2. Create your feature branch (`git checkout -b feature/new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add new feature'`)
63
+ 4. Push to the branch (`git push origin feature/new-feature`)
64
+ 5. Create a new Pull Request
65
+
66
+ ## License
67
+
68
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
69
+
70
+ ## Contact
71
+
72
+ For any questions or suggestions, please open an issue.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'minitest/test_task'
3
+
4
+ Minitest::TestTask.create
5
+
6
+ task default: :test
data/exe/main.rb ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require_relative '../lib/deploymatic'
5
+
6
+ COMMANDS = {
7
+ install: 'Install the service on the remote host',
8
+ uninstall: 'Uninstall the service',
9
+ deploy: 'Deploy the latest version of the service',
10
+ start: 'Start the service',
11
+ stop: 'Stop the service',
12
+ restart: 'Restart the service',
13
+ status: 'Show the current status of the service',
14
+ show: 'Display systemd unit service file of the service'
15
+ }.freeze
16
+
17
+ options = {}
18
+ parser = OptionParser.new do |opts|
19
+ opts.banner = "usage: #{$PROGRAM_NAME} [OPTIONS] {COMMAND}\n\n"
20
+ opts.banner += "Options:\n"
21
+
22
+ opts.on('-v', 'Print version') do |v|
23
+ options[:version] = v
24
+ end
25
+
26
+ opts.separator ''
27
+ opts.separator "Commands:\n"
28
+ COMMANDS.each do |command, desc|
29
+ opts.separator format(' %-10{command} %{desc}s', command: command, desc: desc)
30
+ end
31
+ end
32
+
33
+ begin
34
+ parser.parse!
35
+ rescue OptionParser::InvalidOption
36
+ puts parser.help
37
+ exit 1
38
+ end
39
+
40
+ if options[:version]
41
+ puts Deploymatic::VERSION
42
+ exit 0
43
+ end
44
+
45
+ if ARGV.size != 1
46
+ puts parser.help
47
+ exit 1
48
+ end
49
+
50
+ command = ARGV.first
51
+ unless COMMANDS.key?(command.to_sym)
52
+ puts parser.help
53
+ exit 1
54
+ end
55
+
56
+ runner = Deploymatic::Deployer.new('.matic.yml')
57
+ runner.send(command)
@@ -0,0 +1,52 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module Deploymatic
4
+ class InvalidConfValueError < StandardError; end
5
+
6
+ Conf = Struct.new(
7
+ :name,
8
+ :ssh_user,
9
+ :ssh_host,
10
+ :ssh_port,
11
+ :repo,
12
+ :install_dir,
13
+ :install_commands,
14
+ :start_command,
15
+ :stop_command,
16
+ :log_path,
17
+ :run_after,
18
+ :start_limit_burst,
19
+ :start_limit_interval_seconds,
20
+ :enviroment_variables
21
+ ) do
22
+ def initialize(**args)
23
+ super(**args)
24
+ self.name = self.name.parameterize
25
+ self.install_dir ||= "$HOME/#{self.name}"
26
+ end
27
+
28
+ def check_required_fields
29
+ %i[name ssh_user ssh_host repo start_command install_dir].select do |field|
30
+ self.send(field).nil?
31
+ end
32
+ end
33
+
34
+ def check_required_fields!
35
+ fields = check_required_fields
36
+ case fields.size
37
+ when 0
38
+ nil
39
+ when 1
40
+ raise InvalidConfValueError.new("Invalid conf value: #{fields.first} is not valid.")
41
+ else
42
+ raise InvalidConfValueError.new("Invalid conf values: #{fields.join(', ')} are not valid.")
43
+ end
44
+ end
45
+
46
+ def url
47
+ url = "#{self.ssh_user}@#{ssh_host}"
48
+ url += ":#{self.ssh_port}" if self.ssh_port
49
+ return url
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,133 @@
1
+ require 'debug'
2
+ require 'erb'
3
+ require 'ostruct'
4
+ require 'sshkit'
5
+ require 'sshkit/dsl'
6
+ require 'yaml'
7
+
8
+ module Deploymatic
9
+ class DeploymaticError < StandardError; end
10
+
11
+ class Deployer
12
+ include SSHKit::DSL
13
+
14
+ SSHKit::Backend::Netssh.configure do |ssh|
15
+ ssh.connection_timeout = 30
16
+ ssh.ssh_options = {
17
+ auth_methods: %w[publickey],
18
+ keepalive: true
19
+ }
20
+ end
21
+
22
+ def initialize(config_file_path)
23
+ @conf = Conf.new(**YAML.load_file(config_file_path))
24
+ @conf.check_required_fields!
25
+
26
+ result, ok = run(:echo, '$HOME')
27
+ raise DeploymaticError.new("Unable to retrieve the user's home directory on the remote host: #{result}") unless ok
28
+
29
+ @home_dir = result.strip
30
+ @conf.install_dir.gsub!('$HOME', @home_dir)
31
+ end
32
+
33
+ def install
34
+ # puts systemd_unit_file_to_string
35
+ _, ok = run(:systemd, '--version')
36
+ raise DeploymaticError.new('systemd is not installed on the remote host') unless ok
37
+
38
+ _, ok = run(:git, '--version')
39
+ raise DeploymaticError.new('git is not installed on the remote host') unless ok
40
+
41
+ result, ok = run(:ls, '/var/lib/systemd/linger/')
42
+ raise DeploymaticError.new("User #{@conf.ssh_user} is not in the lingering list") if !ok || !result.include?(@conf.ssh_user)
43
+
44
+ result, ok = run(:mkdir, '-p', @conf.install_dir)
45
+ raise DeploymaticError.new("Cannot create directory '#{@conf.install_dir}': #{result}") unless ok
46
+
47
+ result, ok = run(:git, 'clone', @conf.repo, '.', within: @conf.install_dir)
48
+ raise DeploymaticError.new("Cannot clone git repo '#{@conf.repo}': #{result}") unless ok
49
+
50
+ @conf.install_commands.to_a.each do |install_command|
51
+ result, ok = run(*install_command.split(/\s+/), within: @conf.install_dir)
52
+ raise DeploymaticError.new("Cannot run install command '#{install_command}': #{result}") unless ok
53
+ end
54
+
55
+ io = StringIO.new(systemd_unit_file_to_string)
56
+ path = unit_file_path
57
+ on(@conf.url) do
58
+ upload!(io, path)
59
+ end
60
+
61
+ result, ok = run(:systemctl, '--user', 'daemon-reload')
62
+ raise DeploymaticError.new("Cannot reload systemd daemon: #{result}") unless ok
63
+
64
+ result, ok = run(:systemctl, '--user', 'enable', @conf.name)
65
+ raise DeploymaticError.new("Cannot enable systemd service '#{@conf.name}': #{result}") unless ok
66
+ end
67
+
68
+ def uninstall
69
+ run(:systemctl, '--user', 'stop', @conf.name)
70
+ run(:systemctl, '--user', 'disable', @conf.name)
71
+ run(:rm, unit_file_path)
72
+ run(:systemctl, '--user', 'daemon-reload')
73
+ run(:rm, '-r', @conf.install_dir)
74
+ end
75
+
76
+ def deploy
77
+ result, ok = run(:git, 'pull', within: @conf.install_dir)
78
+ raise DeploymaticError.new("Cannot pull git repo '#{@conf.repo}': #{result}") unless ok
79
+
80
+ @conf.install_commands.to_a.each do |install_command|
81
+ result, ok = run(*install_command.split(/\s+/), within: @conf.install_dir)
82
+ raise DeploymaticError.new("Cannot run install command '#{install_command}': #{result}") unless ok
83
+ end
84
+
85
+ restart
86
+ end
87
+
88
+ %w[start stop restart].each do |command|
89
+ define_method command do
90
+ result, ok = run(:systemctl, '--user', command, @conf.name)
91
+ raise DeploymaticError.new("Cannot #{command} systemd service '#{@conf.name}': #{result}") unless ok
92
+ end
93
+ end
94
+
95
+ def status
96
+ result, ok = run(:systemctl, '--user', 'status', @conf.name)
97
+ raise DeploymaticError.new("Cannot #{command} systemd service '#{@conf.name}': #{result}") unless ok
98
+
99
+ puts result
100
+ end
101
+
102
+ def show
103
+ puts systemd_unit_file_to_string
104
+ end
105
+
106
+ private
107
+
108
+ def systemd_unit_file_to_string
109
+ raw_template = File.read(File.join(__dir__, 'systemd_service_file_template.erb'))
110
+ template = ERB.new(raw_template, trim_mode: '-')
111
+ return template.result(@conf.instance_eval { binding })
112
+ end
113
+
114
+ def run(command, *, within: '.')
115
+ result = nil
116
+ ok = false
117
+ on(@conf.url) do
118
+ within(within) do
119
+ result = capture(command, *, verbosity: Logger::INFO)
120
+ ok = true
121
+ rescue SSHKit::Command::Failed => e
122
+ result = e.message
123
+ ok = false
124
+ end
125
+ end
126
+ return result, ok
127
+ end
128
+
129
+ def unit_file_path
130
+ File.join(@home_dir, '.config/systemd/user', "#{@conf.name}.service").to_s
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,30 @@
1
+ [Unit]
2
+ Description=<%= name %>
3
+
4
+ [Service]
5
+ Type=simple
6
+ Restart=always
7
+ WorkingDirectory=<%= install_dir.gsub('$HOME', '%h') %>
8
+ ExecStart=<%= start_command %>
9
+ <% if stop_command -%>
10
+ ExecStop=<%= stop_command %>
11
+ <% end -%>
12
+ <% if log_path -%>
13
+ StandardOutput=append:<%= log_path %>
14
+ StandardError=append:<%= log_path %>
15
+ <% end -%>
16
+ <% if run_after -%>
17
+ After=<%= after %>
18
+ <% end -%>
19
+ <% if start_limit_burst -%>
20
+ StartLimitBurst=<%= start_limit_burst %>
21
+ <% end -%>
22
+ <%- if start_limit_interval_seconds -%>
23
+ StartLimitIntervalSec=<%= start_limit_interval_seconds %>
24
+ <%- end -%>
25
+ <% enviroment_variables.to_h.each do |key, value| -%>
26
+ Environment=<%= key %>=<%= value %>
27
+ <% end -%>
28
+
29
+ [Install]
30
+ WantedBy=default.target
@@ -0,0 +1,3 @@
1
+ module Deploymatic
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'deploymatic/conf'
2
+ require_relative 'deploymatic/deployer'
3
+ require_relative 'deploymatic/version'
4
+
5
+ module Deploymatic
6
+ end
@@ -0,0 +1,4 @@
1
+ module Deploymatic
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deploymatic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - pioz
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sshkit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.22'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.22'
41
+ description: |
42
+ This Ruby gem streamlines the deployment and management of services within a
43
+ Git repository by leveraging systemd. Designed to simplify the process of
44
+ installing and updating services, the gem enables automated deployment
45
+ operations. It integrates systemd functionalities to manage services as
46
+ daemons, allowing for monitoring, starting, stopping, and restarting services
47
+ directly through Git commands. Configuration is set up quickly and easily in
48
+ a YAML file, ensuring a user-friendly experience. The configuration and use
49
+ of this gem ensure greater operational efficiency, reducing the time and
50
+ effort required for manual service management.
51
+ email:
52
+ - epilotto@gmx.com
53
+ executables:
54
+ - main.rb
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - ".rubocop.yml"
59
+ - LICENSE.txt
60
+ - README.md
61
+ - Rakefile
62
+ - exe/main.rb
63
+ - lib/deploymatic.rb
64
+ - lib/deploymatic/conf.rb
65
+ - lib/deploymatic/deployer.rb
66
+ - lib/deploymatic/systemd_service_file_template.erb
67
+ - lib/deploymatic/version.rb
68
+ - sig/deploymatic.rbs
69
+ homepage: https://github.com/pioz/deploymatic
70
+ licenses:
71
+ - MIT
72
+ metadata:
73
+ homepage_uri: https://github.com/pioz/deploymatic
74
+ source_code_uri: https://github.com/pioz/deploymatic
75
+ rubygems_mfa_required: 'true'
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 3.2.0
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.5.3
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Deploy services on a Git repository using Systemd
95
+ test_files: []