Skip to content

Commit 90758c0

Browse files
authored
release: prepare v1.2-2 (#198)
2 parents fc65edb + 06a77bc commit 90758c0

File tree

24 files changed

+328
-206
lines changed

24 files changed

+328
-206
lines changed

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Thank you for considering contributing to Proxmox-GitOps.
44

55
This document provides guidelines for contributing. These are conventions, not strict mandates; feel free to propose improvements to this document via a pull request.
66

7-
This project is governed by the [Code of Conduct](.github/CODE_OF_CONDUCT.md) and released under the [MIT License](LICENSE). By participating, contributors agree to uphold these terms.
7+
This project is governed by the [Code of Conduct](CODE_OF_CONDUCT.md) and released under the [MIT License](../LICENSE). By participating, contributors agree to uphold these terms.
88

99
### Workflow
1010
- Branching: Fork the repository and create a branch from `develop`. The `main` branch is for stable releases, while `develop` is the active integration branch.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ local/*.hash
66

77
.DS_Store
88
.idea
9+
10+
# git add --force '**/*.local*'
11+
12+
*.local.caddy

config/attributes/default.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
default['title'] = "Proxmox-GitOps"
2+
default['online'] = "https://github.com/stevius10/Proxmox-GitOps"
3+
default['version'] = "v1.2-2"
4+
15
default['id'] = ENV['ID']
26
default['host'] = (default['ip'] = ENV['IP'].to_s.presence || "127.0.0.1")
37
default['key'] = ENV['KEY'].to_s.presence || "/share/.ssh/#{node['id']}"
@@ -34,3 +38,5 @@
3438
default['runner']['dir']['cache'] = '/tmp'
3539

3640
default['runner']['conf']['label'] = 'shell'
41+
42+
default['runner']['source'] = 'https://gitea.com/gitea/act_runner/releases'

config/libraries/common.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def self.application(ctx, name, user: nil, group: nil,
107107
(is_active = Mixlib::ShellOut.new(verify_cmd)).run_command
108108
is_active.exitstatus.zero? ? (ok = true; break) : (sleep verify_interval)
109109
end
110-
Logs.error("service '#{name}' failed health check") unless ok
110+
Logs.error!("service '#{name}' failed health check") unless ok
111111
end
112112
action :nothing
113113
subscribes :run, "service[#{name}]", :delayed if verify

config/libraries/constants.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ module Constants
99
'Content-Type' => 'application/x-www-form-urlencoded'
1010
}.freeze
1111

12-
end
12+
URI_GITHUB_BASE = "https://api.github.com".freeze
13+
URI_GITHUB_LATEST = ->(owner, repo) { "#{URI_GITHUB_BASE}/repos/#{owner}/#{repo}/releases/latest" }
14+
URI_GITHUB_TAG = ->(owner, repo, tag) { tag.blank? ? nil : "#{URI_GITHUB_BASE}/repos/#{owner}/#{repo}/releases/tags/#{'v' unless tag.start_with?('v')}#{tag}" }
15+
16+
end

config/libraries/default.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ def self.config(ctx, default: nil)
1515
@config ||= (default.presence ? 'config' : presence_or(Env.get(node, "app_config"), config(node, default: true))).to_s
1616
end
1717

18+
def self.snapshot_dir(ctx, default: nil)
19+
node = Ctx.node(ctx)
20+
@snapshot_dir ||= (default.presence ? '/share/snapshots' : presence_or(Env.get(node, "app_snapshot_dir"), snapshot_dir(node, default: true))).to_s
21+
end
22+
1823
def self.presence_or(var, default)
1924
var.to_s.presence || default.to_s
2025
end

config/libraries/logs.rb

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,28 @@ def self.log(msg, level: :info)
1111
end
1212

1313
def self.info(msg); log(msg) end; def self.warn(msg); log(msg, level: :warn) end
14-
def self.error(msg, raise: true); log(msg, level: :error); raise msg if raise end
14+
def self.error(msg, raise: false); log(msg, level: :error); raise msg if raise end
15+
def self.error!(msg); error(msg, raise: true) end
1516
def self.info?(msg, result: true); log(msg); result; end
1617
def self.request(uri, response); info("requested #{uri}: #{response&.code} #{response&.message}"); return response end
1718
def self.return(msg); log(msg.to_s); return msg end
19+
def self.returns(msg, result, level: :info); log(msg.to_s, level: level); return result end
1820

1921
def self.debug(msg, *pairs, ctx: nil, level: :info)
2022
flat = pairs.flatten
2123
raise ArgumentError, "debug requires key value pairs (#{flat.length}: #{flat.inspect})" unless flat.length.even?
2224
input = flat.each_slice(2).to_h.transform_keys(&:to_s)
2325
payload = input.map { |k, v| "#{k}=#{v.inspect}" }.join(" ")
2426
log([msg, payload].reject { |s| s.blank? }.join(" "), level: level)
25-
log(Ctx.node(ctx), level: 'debug') if ctx
27+
log("#{ctx.cookbook_name}::#{ctx.recipe_name}", level: :debug) \
28+
if ctx && ctx.respond_to?(:cookbook_name) && ctx.respond_to?(:recipe_name)
2629
end
2730

2831
def self.try!(msg, *pairs, ctx: nil, raise: false)
29-
yield
32+
return yield
3033
rescue => exception
3134
debug("failed: #{msg}: #{exception.message}", *(pairs.flatten), ctx: ctx, level: (raise ? :error : :warn))
35+
# debug(*(pairs.flatten), ctx: ctx, level: :debug)
3236
raise("[#{method_label(callsite)}] #{exception.message} #{msg}") if raise
3337
end
3438

@@ -44,6 +48,13 @@ def self.request!(uri, response, valid=[], msg: nil, ctx: nil)
4448
return response
4549
end
4650

51+
# Raise
52+
53+
def self.raise_if_blank(msg, value)
54+
error!(msg) if value.nil? || (value.respond_to?(:empty?) && value.empty?)
55+
value
56+
end
57+
4758
# Helper
4859

4960
FORMAT_WITH = "\e[1m[%s] %s (%s:%d)\e[0m"

config/libraries/utils.rb

Lines changed: 90 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
require_relative 'common'
2+
require_relative 'constants'
3+
14
require 'digest'
25
require 'fileutils'
36
require 'json'
@@ -53,51 +56,49 @@ def self.wait(condition = nil, timeout: 20, sleep_interval: 5, &block)
5356

5457
# System
5558

56-
def self.arch(ctx)
57-
case ctx['kernel']['machine'].to_s
58-
when /arm64|aarch64/
59-
'arm64'
60-
when /armv6|armv7l/
61-
'armv7'
62-
else
63-
'amd64'
64-
end
59+
def self.arch
60+
{ 'x86_64'=>'amd64', 'aarch64'=>'arm64', 'arm64'=>'arm64', 'armv7l'=>'armv7' }.fetch(`uname -m`.strip, 'amd64')
6561
end
6662

67-
def self.snapshot(ctx, dir, snapshot_dir: '/share/snapshots', name: ctx.cookbook_name, restore: false, user: Default.user(ctx), group: Default.group(ctx), mode: 0o755)
68-
timestamp = Time.now.strftime('%H%M-%d%m%y')
69-
snapshot = File.join(snapshot_dir, name, "#{name}-#{timestamp}.tar.gz")
63+
def self.snapshot(ctx, data_dir, name: ctx.cookbook_name, restore: false, user: Default.user(ctx), group: Default.group(ctx), snapshot_dir: Default.snapshot_dir(ctx), mode: 0o755)
64+
65+
snapshot_dir = "#{snapshot_dir}/#{name}"
66+
snapshot = File.join(snapshot_dir, "#{name}-#{Time.now.strftime('%H%M-%d%m%y')}.tar.gz")
67+
7068
md5_dir = ->(path) {
7169
entries = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
7270
files = entries.reject { |f| File.directory?(f) || File.symlink?(f) || ['.', '..'].include?(File.basename(f)) || File.basename(f).start_with?('._') }
7371
Digest::MD5.new.tap { |md5| files.sort.each { |f| File.open(f, 'rb') { |io| md5.update(io.read) } } }.hexdigest }
72+
7473
verify = ->(archive, compare_dir) {
7574
Dir.mktmpdir do |tmp|
7675
Logs.try!("snapshot extraction", [:archive, archive, :tmp, tmp], raise: true) do
7776
system("tar -xzf #{Shellwords.escape(archive)} -C #{Shellwords.escape(tmp)}") or raise("snapshot verification failed")
7877
end
7978
raise("verify snapshot failed") unless md5_dir.(tmp) == (Dir.exist?(compare_dir) ? md5_dir.(compare_dir) : '')
80-
end
81-
true
82-
}
79+
end; true }
80+
8381
if restore
84-
latest = Dir[File.join(snapshot_dir, name, "#{name}-*.tar.gz")].max_by { |f| [File.mtime(f), File.basename(f)] }
82+
latest = Dir[File.join(snapshot_dir, "#{name}-*.tar.gz")].max_by { |f| [File.mtime(f), File.basename(f)] }
8583
if latest && ::File.exist?(latest)
86-
FileUtils.rm_rf(dir)
87-
FileUtils.mkdir_p(dir)
88-
Logs.try!("snapshot restore", [:dir, dir, :archive, latest], raise: true) do
89-
system("tar -xzf #{Shellwords.escape(latest)} -C #{Shellwords.escape(dir)}") or raise("tar extract failed")
84+
FileUtils.rm_rf(data_dir)
85+
FileUtils.mkdir_p(data_dir)
86+
Logs.try!("snapshot restore", [:app_dir, data_dir, :archive, latest], raise: true) do
87+
system("tar -xzf #{Shellwords.escape(latest)} -C #{Shellwords.escape(data_dir)}") or raise("tar extract failed")
9088
end
91-
FileUtils.chown_R(user, group, dir)
92-
FileUtils.chmod_R(mode, dir)
89+
FileUtils.chown_R(user, group, data_dir)
90+
FileUtils.chmod_R(mode, data_dir)
9391
end
92+
return true
9493
end
95-
return true unless Dir.exist?(dir) # true to be idempotent integrable before installation
94+
return true unless Dir.exist?(data_dir) && !Dir.glob("#{data_dir}/*").empty? # true to be idempotent integrable before installation
95+
9696
FileUtils.mkdir_p(File.dirname(snapshot))
97-
Logs.try!("snapshot creation", [:dir, dir, :snapshot, snapshot], raise: true) do
98-
system("tar -czf #{Shellwords.escape(snapshot)} -C #{Shellwords.escape(dir)} .") or raise("tar compress failed")
97+
Logs.try!("snapshot creation", [:data_dir, data_dir, :snapshot, snapshot], raise: true) do
98+
system("tar -czf #{Shellwords.escape(snapshot)} -C #{Shellwords.escape(data_dir)} .") or raise("tar compress failed")
9999
end
100-
return verify.(snapshot, dir)
100+
101+
return verify.(snapshot, data_dir)
101102
end
102103

103104
# Remote
@@ -140,51 +141,85 @@ def self.proxmox(ctx, path)
140141
request(url, headers: headers).json['data']
141142
end
142143

143-
def self.install(ctx, uri, app_dir, data_dir, version_dir: "/app", snapshot_dir: '/share/snapshots')
144-
version_file = File.join(version_dir, '.version')
144+
def self.install(ctx, owner:, repo:, app_dir:, name: nil, version: 'latest', user: Default.user(ctx), group: Default.group(ctx), extract: true)
145+
version_file = File.join(app_dir, '.version')
145146
version_installed = ::File.exist?(version_file) ? ::File.read(version_file).strip : nil
146-
version = latest(uri, version_installed)
147-
Common.directories(ctx, app_dir, recreate: version)
148-
snapshot(ctx, data_dir, snapshot_dir: snapshot_dir) if version
149-
return false unless version
147+
release = nil
148+
149+
FileUtils.mkdir_p(app_dir)
150+
assets = ->(a) { a[:name].match?(/linux[-_]#{Utils.arch}/i) && !a[:name].end_with?('.asc', '.sha265', '.pem') }
151+
152+
if version == 'latest'
153+
release = Logs.raise_if_blank("check latest", latest(owner, repo))
154+
release = release.first if release.is_a?(Array)
155+
return false unless release
156+
version = release[:tag_name].to_s.gsub(/^v/, '')
157+
end
150158

151-
ctx.file version_file do
159+
if version_installed.nil?
160+
Logs.info("initial installation (version '#{version}')")
161+
elsif Gem::Version.new(version) > Gem::Version.new(version_installed)
162+
Logs.info("update from '#{version_installed}' to '#{version}'")
163+
else
164+
return Logs.info?("no update required from '#{version_installed}' to '#{version}'", result: false)
165+
end
166+
167+
unless release
168+
uri = Constants::URI_GITHUB_TAG.call(owner, repo, version)
169+
release = Logs.try!("get release by tag", [:uri, uri]) do
170+
response = request(uri, headers: { 'Accept' => 'application/vnd.github+json' }, log: false)
171+
response.is_a?(Net::HTTPSuccess) ? response.json(symbolize_names: true) : nil
172+
end
173+
return Logs.returns("no release for '#{version}'", false) unless release
174+
end
175+
176+
download_url, filename = (if (asset = release[:assets].find(&assets))
177+
[asset[:browser_download_url], File.basename(URI.parse(asset[:browser_download_url]).path)]
178+
else [release[:tarball_url], "#{repo}-#{version}.tar.gz"]
179+
end); Logs.raise_if_blank(download_url, "missing asset for '#{version}'")
180+
181+
Dir.mktmpdir do |tmpdir|
182+
archive_path = File.join(tmpdir, filename)
183+
Logs.try!("download asset #{download_url}", [:to, archive_path]) { download(ctx, archive_path, url: download_url) }
184+
185+
if extract && archive_path.end_with?('.tar.gz', '.tgz', '.zip')
186+
(system("tar -xzf #{Shellwords.escape(archive_path)} --strip-components=1 -C #{Shellwords.escape(app_dir)}") or
187+
raise "tar extract failed for #{archive_path}") if extract
188+
else # Binary
189+
FileUtils.mv(archive_path, File.join(app_dir, name || repo))
190+
end
191+
192+
FileUtils.chown_R(user, group, app_dir)
193+
FileUtils.chmod_R(0755, app_dir)
194+
end
195+
196+
Ctx.dsl(ctx).file version_file do
152197
content version.to_s
153198
owner Default.user(ctx)
154199
group Default.group(ctx)
155-
mode 775
200+
mode '0755'
156201
action :create
157202
end
158-
159203
return version
204+
160205
end
161206

162207
def self.download(ctx, path, url:, owner: Default.user(ctx), group: Default.group(ctx), mode: '0754', action: :create)
163-
ctx.remote_file path do
164-
source url.respond_to?(:call) ? lazy { url.call } : url
208+
Common.directories(Ctx.dsl(ctx), File.dirname(path), owner: owner, group: group, mode: mode)
209+
Ctx.dsl(ctx).remote_file path do
210+
source url.respond_to?(:call)? lazy { url.call } : url
165211
owner owner
166212
group group
167213
mode mode
168214
action action
169-
end
215+
end.run_action(action)
170216
end
171217

172-
def self.latest(url, installed_version = nil)
173-
latest_version = (request(url).body[/title>.*?v?([0-9]+\.[0-9]+(?:\.[0-9]+)?)/, 1] || "latest").to_s
174-
Logs.info("latest version: '#{latest_version}' (#{url})")
175-
176-
if installed_version.nil?
177-
Logs.info("initial installation (version '#{latest_version}')")
178-
return latest_version
179-
end
180-
181-
if Gem::Version.new(latest_version) > Gem::Version.new(installed_version)
182-
Logs.info("update from '#{installed_version}' to '#{latest_version}'")
183-
return latest_version
184-
else
185-
Logs.info("no update required from version '#{installed_version}' to '#{latest_version}'")
186-
return false
187-
end
218+
def self.latest(owner, repo)
219+
api_url = Constants::URI_GITHUB_LATEST.call(owner, repo)
220+
response = request(api_url, headers: { 'Accept' => 'application/vnd.github+json' }, log: false)
221+
return false unless response.is_a?(Net::HTTPSuccess)
222+
response.json(symbolize_names: true)
188223
end
189224

190-
end
225+
end

config/recipes/customize.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
owner node['app']['user']
1414
group node['app']['group']
1515
mode '0644'
16+
variables(title: node['title'])
1617
end
1718
end
1819

@@ -31,5 +32,6 @@
3132
owner node['app']['user']
3233
group node['app']['group']
3334
mode '0644'
35+
variables(title: node['title'], online: node['online'], version: node['version'])
3436
end
3537
end

config/recipes/git.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
Utils.download(self, "#{node['git']['dir']['app']}/gitea",
2-
url: -> { ver = Utils.latest('https://github.com/go-gitea/gitea/releases/latest')
3-
"https://github.com/go-gitea/gitea/releases/download/v#{ver}/gitea-#{ver}-linux-#{Utils.arch(node)}" } )
1+
ruby_block 'git_install' do block do
2+
Utils.install(self, owner: "go-gitea", repo: "gitea", app_dir: node['git']['dir']['app'], name: "gitea")
3+
end end
44

55
template "#{node['git']['dir']['app']}/app.ini" do
66
source 'git_app.ini.erb'
@@ -15,8 +15,9 @@
1515

1616
include_recipe('config::customize') if node['git']['conf']['customize']
1717

18-
Common.application(self, cookbook_name,
19-
user: node['app']['user'] , cwd: node['git']['dir']['data'],
20-
exec: "#{node['git']['dir']['app']}/gitea web --config #{node['git']['dir']['app']}/app.ini --custom-path #{node['git']['dir']['custom']}",
21-
unit: { 'Service' => { 'Environment' => "USER=#{node['app']['user'] } HOME=#{node['git']['dir']['home']}" } },
22-
subscribe: ["template[#{node['git']['dir']['app']}/app.ini]", "remote_file[#{node['git']['dir']['app']}/gitea]"] )
18+
ruby_block "#{self.recipe_name}_application" do block do
19+
Common.application(node, cookbook_name, user: node['app']['user'] , cwd: node['git']['dir']['data'],
20+
exec: "#{node['git']['dir']['app']}/gitea web --config #{node['git']['dir']['app']}/app.ini --custom-path #{node['git']['dir']['custom']}",
21+
unit: { 'Service' => { 'Environment' => "USER=#{node['app']['user'] } HOME=#{node['git']['dir']['home']}" } },
22+
subscribe: ["template[#{node['git']['dir']['app']}/app.ini]", "remote_file[#{node['git']['dir']['app']}/gitea]"] )
23+
end end

0 commit comments

Comments
 (0)