motd_forge 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 +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +8 -0
- data/exe/motd-forge +6 -0
- data/lib/motd_forge/banner.rb +53 -0
- data/lib/motd_forge/cli.rb +68 -0
- data/lib/motd_forge/quotes.rb +35 -0
- data/lib/motd_forge/system_stats.rb +72 -0
- data/lib/motd_forge/version.rb +5 -0
- data/lib/motd_forge.rb +11 -0
- data/motd_forge.gemspec +30 -0
- metadata +99 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4f464272c1529564c2149c1ca24df6cfcc2206abed5339a63cbaf024411e16a2
|
|
4
|
+
data.tar.gz: b36d08dacd1857f2d4d40d06b23d31ed9274b6cfebe0e2a0a190455da24cf071
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 64a34ddd860c4fd8f26146b7db2617fd53144f03e9822f809bd3865c2e131114c61a5e1b8e4c6b20e4301d9b00a96c81fdbc9f883fb1a3394807a6c94111f1cd
|
|
7
|
+
data.tar.gz: d17e49efd869e98c0c975e90f31abba782ba8ab196b811171804eacc39fea4c4b8d32d530e1f0cef8e6791ff30695a12ceba0d0ca03d9343adbe4a315bc2ab8b
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zach Schneider
|
|
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,50 @@
|
|
|
1
|
+
# motd-forge
|
|
2
|
+
|
|
3
|
+
Generate a randomized login banner (MOTD) for your servers: a title (rendered
|
|
4
|
+
big via `figlet` if it's installed, plain text otherwise), current uptime,
|
|
5
|
+
disk and memory usage, and a random quote.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
$ gem install motd_forge
|
|
10
|
+
|
|
11
|
+
Or from source:
|
|
12
|
+
|
|
13
|
+
$ bundle install
|
|
14
|
+
$ rake install
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
$ motd-forge
|
|
19
|
+
$ motd-forge --name mybox
|
|
20
|
+
$ motd-forge --quote-file ~/.motd-quotes.txt
|
|
21
|
+
$ motd-forge --no-quote
|
|
22
|
+
|
|
23
|
+
Run `motd-forge --help` for the full list of options.
|
|
24
|
+
|
|
25
|
+
### Config file
|
|
26
|
+
|
|
27
|
+
Settings can also live in `~/.motd-forge.yml` (or a path passed via `--config`):
|
|
28
|
+
|
|
29
|
+
title: mybox
|
|
30
|
+
quote_file: ~/.motd-quotes.txt
|
|
31
|
+
|
|
32
|
+
CLI flags take precedence over the config file.
|
|
33
|
+
|
|
34
|
+
### Wiring it into a login banner
|
|
35
|
+
|
|
36
|
+
Add a call to `motd-forge` from `/etc/profile.d/motd-forge.sh` (or your
|
|
37
|
+
shell's rc file) so it prints on every SSH login:
|
|
38
|
+
|
|
39
|
+
#!/bin/sh
|
|
40
|
+
motd-forge
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
$ bundle install
|
|
45
|
+
$ rake spec # run the test suite
|
|
46
|
+
$ bin/console # interactive prompt
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
data/Rakefile
ADDED
data/exe/motd-forge
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
|
|
5
|
+
module MotdForge
|
|
6
|
+
# Composes the final banner: a big title (via `figlet` if it's installed,
|
|
7
|
+
# else a plain bordered header), system stats, and a random quote.
|
|
8
|
+
class Banner
|
|
9
|
+
def initialize(title:, stats: SystemStats.capture, quote: Quotes.new.pick)
|
|
10
|
+
@title = title
|
|
11
|
+
@stats = stats
|
|
12
|
+
@quote = quote
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render
|
|
16
|
+
[render_title, render_stats, render_quote].compact.join("\n")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def render_title
|
|
22
|
+
figlet_title || plain_title
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def figlet_title
|
|
26
|
+
return unless figlet_available?
|
|
27
|
+
|
|
28
|
+
output = `figlet #{Shellwords.escape(@title)} 2>/dev/null`
|
|
29
|
+
output.empty? ? nil : output.rstrip
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def figlet_available?
|
|
33
|
+
return @figlet_available if defined?(@figlet_available)
|
|
34
|
+
|
|
35
|
+
@figlet_available = system("which figlet > /dev/null 2>&1")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def plain_title
|
|
39
|
+
border = "=" * (@title.length + 4)
|
|
40
|
+
"#{border}\n| #{@title} |\n#{border}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_stats
|
|
44
|
+
"Uptime: #{@stats.uptime} | " \
|
|
45
|
+
"Disk: #{@stats.disk_usage_percent} used | " \
|
|
46
|
+
"Mem: #{@stats.mem_used}/#{@stats.mem_total} (#{@stats.mem_used_percent})"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def render_quote
|
|
50
|
+
%("#{@quote}") if @quote
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "socket"
|
|
6
|
+
|
|
7
|
+
module MotdForge
|
|
8
|
+
# Parses CLI flags, merges in an optional YAML config, and prints the banner.
|
|
9
|
+
class CLI
|
|
10
|
+
DEFAULT_CONFIG_PATH = File.expand_path("~/.motd-forge.yml")
|
|
11
|
+
|
|
12
|
+
def self.run(argv = ARGV)
|
|
13
|
+
new(argv).run
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(argv)
|
|
17
|
+
@argv = argv
|
|
18
|
+
@title = Socket.gethostname
|
|
19
|
+
@title_from_cli = false
|
|
20
|
+
@quote_file = nil
|
|
21
|
+
@show_quote = true
|
|
22
|
+
@config_path = DEFAULT_CONFIG_PATH
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run
|
|
26
|
+
parse_options!
|
|
27
|
+
load_config!
|
|
28
|
+
|
|
29
|
+
quote = @show_quote ? Quotes.new(file: @quote_file).pick : nil
|
|
30
|
+
puts Banner.new(title: @title, quote: quote).render
|
|
31
|
+
0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def parse_options!
|
|
37
|
+
OptionParser.new do |opts|
|
|
38
|
+
opts.banner = "Usage: motd-forge [options]"
|
|
39
|
+
|
|
40
|
+
opts.on("-n", "--name NAME", "Title to render (default: hostname)") do |v|
|
|
41
|
+
@title = v
|
|
42
|
+
@title_from_cli = true
|
|
43
|
+
end
|
|
44
|
+
opts.on("-q", "--quote-file FILE", "Custom quotes file, one per line") { |v| @quote_file = v }
|
|
45
|
+
opts.on("--no-quote", "Don't print a quote") { @show_quote = false }
|
|
46
|
+
opts.on("-c", "--config FILE", "Config file (default: #{DEFAULT_CONFIG_PATH})") { |v| @config_path = v }
|
|
47
|
+
opts.on("-v", "--version", "Print the version") do
|
|
48
|
+
puts MotdForge::VERSION
|
|
49
|
+
exit 0
|
|
50
|
+
end
|
|
51
|
+
opts.on("-h", "--help", "Print this help") do
|
|
52
|
+
puts opts
|
|
53
|
+
exit 0
|
|
54
|
+
end
|
|
55
|
+
end.parse!(@argv)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def load_config!
|
|
59
|
+
return unless File.exist?(@config_path)
|
|
60
|
+
|
|
61
|
+
config = YAML.safe_load(File.read(@config_path)) || {}
|
|
62
|
+
@title = config["title"] if config["title"] && !@title_from_cli
|
|
63
|
+
@quote_file ||= config["quote_file"]
|
|
64
|
+
rescue Psych::SyntaxError
|
|
65
|
+
warn "motd-forge: ignoring invalid config at #{@config_path}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MotdForge
|
|
4
|
+
# Picks a random quote to print under the banner. Ships with a small
|
|
5
|
+
# built-in set and can be extended with a plain-text file, one quote per line.
|
|
6
|
+
class Quotes
|
|
7
|
+
DEFAULT = [
|
|
8
|
+
"It's not a bug, it's an undocumented feature.",
|
|
9
|
+
"There's no place like 127.0.0.1.",
|
|
10
|
+
"Rebooting the router fixes everything, eventually.",
|
|
11
|
+
"In case of fire: git commit, git push, then evacuate.",
|
|
12
|
+
"99 little bugs in the code, 99 little bugs...",
|
|
13
|
+
"The cloud is just someone else's computer. This one's yours."
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def initialize(file: nil)
|
|
17
|
+
@file = file
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pick
|
|
21
|
+
pool.sample
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def pool
|
|
27
|
+
return DEFAULT unless @file
|
|
28
|
+
|
|
29
|
+
lines = File.readlines(@file, chomp: true).reject(&:empty?)
|
|
30
|
+
lines.empty? ? DEFAULT : lines
|
|
31
|
+
rescue Errno::ENOENT
|
|
32
|
+
DEFAULT
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MotdForge
|
|
4
|
+
# Reads uptime, disk, and memory usage from the host. Uses /proc on Linux,
|
|
5
|
+
# with a best-effort shell fallback (e.g. `uptime`) elsewhere.
|
|
6
|
+
class SystemStats
|
|
7
|
+
Snapshot = Struct.new(:uptime, :disk_usage_percent, :mem_used, :mem_total, :mem_used_percent)
|
|
8
|
+
|
|
9
|
+
def self.capture
|
|
10
|
+
new.capture
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def capture
|
|
14
|
+
Snapshot.new(uptime, disk_usage_percent, *memory)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def uptime
|
|
20
|
+
seconds = File.read("/proc/uptime").split.first.to_f
|
|
21
|
+
format_duration(seconds)
|
|
22
|
+
rescue Errno::ENOENT
|
|
23
|
+
shell_uptime
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def format_duration(seconds)
|
|
27
|
+
days, rem = seconds.divmod(86_400)
|
|
28
|
+
hours, rem = rem.divmod(3_600)
|
|
29
|
+
minutes, = rem.divmod(60)
|
|
30
|
+
|
|
31
|
+
parts = []
|
|
32
|
+
parts << "#{days.to_i}d" if days.positive?
|
|
33
|
+
parts << "#{hours.to_i}h" if hours.positive? || days.positive?
|
|
34
|
+
parts << "#{minutes.to_i}m"
|
|
35
|
+
parts.join(" ")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def shell_uptime
|
|
39
|
+
`uptime -p`.strip.sub(/\Aup /, "")
|
|
40
|
+
rescue StandardError
|
|
41
|
+
"unknown"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def disk_usage_percent(path = "/")
|
|
45
|
+
line = `df -kP #{path} 2>/dev/null`.lines[1]
|
|
46
|
+
return "unknown" unless line
|
|
47
|
+
|
|
48
|
+
line.split[4]
|
|
49
|
+
rescue StandardError
|
|
50
|
+
"unknown"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def memory
|
|
54
|
+
meminfo = File.read("/proc/meminfo")
|
|
55
|
+
total_kb = meminfo[/MemTotal:\s+(\d+)/, 1].to_i
|
|
56
|
+
available_kb = meminfo[/MemAvailable:\s+(\d+)/, 1].to_i
|
|
57
|
+
used_kb = total_kb - available_kb
|
|
58
|
+
|
|
59
|
+
percent = total_kb.positive? ? ((used_kb.to_f / total_kb) * 100).round : 0
|
|
60
|
+
[kb_to_human(used_kb), kb_to_human(total_kb), "#{percent}%"]
|
|
61
|
+
rescue Errno::ENOENT
|
|
62
|
+
%w[unknown unknown unknown]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def kb_to_human(kb)
|
|
66
|
+
gb = kb / 1024.0 / 1024.0
|
|
67
|
+
return "#{gb.round(1)}G" if gb >= 1
|
|
68
|
+
|
|
69
|
+
"#{(kb / 1024.0).round}M"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/motd_forge.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "motd_forge/version"
|
|
4
|
+
require_relative "motd_forge/system_stats"
|
|
5
|
+
require_relative "motd_forge/quotes"
|
|
6
|
+
require_relative "motd_forge/banner"
|
|
7
|
+
require_relative "motd_forge/cli"
|
|
8
|
+
|
|
9
|
+
module MotdForge
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
end
|
data/motd_forge.gemspec
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/motd_forge/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "motd_forge"
|
|
7
|
+
spec.version = MotdForge::VERSION
|
|
8
|
+
spec.authors = ["Zach Schneider"]
|
|
9
|
+
spec.email = ["zachschneider@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Generate a randomized login banner (MOTD) with system stats and a quote."
|
|
12
|
+
spec.description = "motd-forge builds a custom message-of-the-day banner showing uptime, " \
|
|
13
|
+
"disk and memory usage, and a random quote -- handy for homelab servers."
|
|
14
|
+
spec.homepage = "https://github.com/zkm/motd_forge"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
17
|
+
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
+
|
|
21
|
+
spec.files = Dir.glob("{lib,exe}/**/*") +
|
|
22
|
+
%w[README.md LICENSE.txt CHANGELOG.md motd_forge.gemspec Gemfile Rakefile]
|
|
23
|
+
spec.bindir = "exe"
|
|
24
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
25
|
+
spec.require_paths = ["lib"]
|
|
26
|
+
|
|
27
|
+
spec.add_development_dependency "erb"
|
|
28
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.13"
|
|
30
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: motd_forge
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Zach Schneider
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: erb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.13'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.13'
|
|
54
|
+
description: motd-forge builds a custom message-of-the-day banner showing uptime,
|
|
55
|
+
disk and memory usage, and a random quote -- handy for homelab servers.
|
|
56
|
+
email:
|
|
57
|
+
- zachschneider@gmail.com
|
|
58
|
+
executables:
|
|
59
|
+
- motd-forge
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- CHANGELOG.md
|
|
64
|
+
- Gemfile
|
|
65
|
+
- LICENSE.txt
|
|
66
|
+
- README.md
|
|
67
|
+
- Rakefile
|
|
68
|
+
- exe/motd-forge
|
|
69
|
+
- lib/motd_forge.rb
|
|
70
|
+
- lib/motd_forge/banner.rb
|
|
71
|
+
- lib/motd_forge/cli.rb
|
|
72
|
+
- lib/motd_forge/quotes.rb
|
|
73
|
+
- lib/motd_forge/system_stats.rb
|
|
74
|
+
- lib/motd_forge/version.rb
|
|
75
|
+
- motd_forge.gemspec
|
|
76
|
+
homepage: https://github.com/zkm/motd_forge
|
|
77
|
+
licenses:
|
|
78
|
+
- MIT
|
|
79
|
+
metadata:
|
|
80
|
+
homepage_uri: https://github.com/zkm/motd_forge
|
|
81
|
+
source_code_uri: https://github.com/zkm/motd_forge
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 3.0.0
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
requirements: []
|
|
96
|
+
rubygems_version: 3.6.9
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Generate a randomized login banner (MOTD) with system stats and a quote.
|
|
99
|
+
test_files: []
|