deploymatic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []