Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
2ce1701039
|
|||
068c3a48f4
|
|||
dd67a7095c
|
|||
3b6c8af8cc
|
|||
b820b7bb4d
|
|||
7d200ec423
|
|||
0ce3e506bb
|
|||
0a5f51ff8f | |||
51a09ffe61
|
|||
526d29bef1
|
|||
d08349f800
|
|||
70a2e47727
|
|||
626e7cbe0b
|
|||
5152598804
|
|||
b8de2dd0b8
|
|||
0ff3a7da59
|
@@ -13,3 +13,6 @@ indent_size = 2
|
|||||||
[{*.ecr,*.json}]
|
[{*.ecr,*.json}]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[README.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
/docs/
|
/docs/
|
||||||
|
/dist/
|
||||||
/lib/
|
/lib/
|
||||||
/bin/
|
/bin/
|
||||||
/.shards/
|
/.shards/
|
||||||
|
@@ -6,6 +6,14 @@
|
|||||||
- ℹ️ - Information
|
- ℹ️ - Information
|
||||||
- ♻️ - Edited
|
- ♻️ - Edited
|
||||||
|
|
||||||
|
## 0.2.6 - [03/08/2022]
|
||||||
|
- ➕ - Added environment variable for custom password file path
|
||||||
|
- ➕ - Added version in to ASCII logo
|
||||||
|
|
||||||
|
## 0.2.5 - [31/07/2022]
|
||||||
|
- ➕ - Added timeout user input
|
||||||
|
- ♻️ - Source file separated
|
||||||
|
|
||||||
## 0.2.4 - [26/07/2022]
|
## 0.2.4 - [26/07/2022]
|
||||||
- ➕ - Added hint for fix file permissions error.
|
- ➕ - Added hint for fix file permissions error.
|
||||||
- ℹ️ - Crystal version raise to 1.5.0 for NO_COLOR support
|
- ℹ️ - Crystal version raise to 1.5.0 for NO_COLOR support
|
||||||
|
20
README.md
20
README.md
@@ -1,20 +1,14 @@
|
|||||||
# The very simple password manager for humans
|
# 🔑 The very simple password manager for humans
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
[](#download)
|
||||||
|
|
||||||
# Screenshots
|
## 📷 Screenshots
|
||||||
|
|
||||||

|
See [SCREENSHOTS.md](SCREENSHOTS.md).
|
||||||
|
|
||||||
# Download
|
## 💾 Download
|
||||||
|
|
||||||
**Coming soon**
|
[](https://data.iiiypuk.me/pmng/alpha/pmng-0.2.6-linux-x86_64.tar.xz)
|
||||||
|
|
||||||
- [Linux x86_64](https://me.a2s.su/.../)
|
|
||||||
- [Linux x86](https://me.a2s.su/.../)
|
|
||||||
- [Linux x86_64 musl](https://me.a2s.su/.../)
|
|
||||||
- [OpenBSD x86_64](https://me.a2s.su/.../)
|
|
||||||
|
5
SCREENSHOTS.md
Normal file
5
SCREENSHOTS.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## 📷 Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
25
build_release.sh
Executable file
25
build_release.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Preparing
|
||||||
|
if [ -d "./dist/" ];
|
||||||
|
then
|
||||||
|
rm dist/* 2> /dev/null
|
||||||
|
else
|
||||||
|
mkdir dist/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect Musl C library
|
||||||
|
# thx @Unmanned Player https://stackoverflow.com/a/60471114
|
||||||
|
|
||||||
|
libc=$(ldd /bin/ls | grep 'musl' | head -1 | cut -d ' ' -f1)
|
||||||
|
if [ -z $libc ];
|
||||||
|
then
|
||||||
|
# build libc
|
||||||
|
OUTPUT=pmng-$(shards version)-linux-x86_64
|
||||||
|
crystal build ./src/pmng.cr --release --progress -o ./dist/$OUTPUT
|
||||||
|
tar -cJf ./dist/$OUTPUT.tar.xz ./dist/$OUTPUT
|
||||||
|
else
|
||||||
|
# Build musl
|
||||||
|
crystal build ./src/pmng.cr --release --progress -o ./dist/pmng-$(shards version)-linux-musl-x86_64
|
||||||
|
fi
|
||||||
|
|
@@ -1,5 +1,5 @@
|
|||||||
name: pmng
|
name: pmng
|
||||||
version: 0.2.4
|
version: 0.2.6
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Alexander Popov <iiiypuk@iiiypuk.me>
|
- Alexander Popov <iiiypuk@iiiypuk.me>
|
||||||
|
171
src/pmng.cr
171
src/pmng.cr
@@ -2,115 +2,53 @@ require "option_parser"
|
|||||||
require "yaml"
|
require "yaml"
|
||||||
require "colorize"
|
require "colorize"
|
||||||
|
|
||||||
# password serializer
|
require "./pmng/*"
|
||||||
class Password
|
require "./pmng/functions/*"
|
||||||
include YAML::Serializable
|
|
||||||
|
|
||||||
@[YAML::Field(key: "url")]
|
module Pmng
|
||||||
property url : String
|
# check password file exists
|
||||||
@[YAML::Field(key: "email")]
|
if File.exists?(PASSWORD_FILE_PATH)
|
||||||
property email : String
|
|
||||||
@[YAML::Field(key: "login")]
|
|
||||||
property login : String
|
|
||||||
@[YAML::Field(key: "password")]
|
|
||||||
property password : String
|
|
||||||
@[YAML::Field(key: "desc")]
|
|
||||||
property desc : String
|
|
||||||
@[YAML::Field(key: "profile_url")]
|
|
||||||
property profile_url : String
|
|
||||||
@[YAML::Field(key: "update")]
|
|
||||||
property update : Int32
|
|
||||||
end
|
|
||||||
|
|
||||||
VERSION = "0.2.4"
|
|
||||||
PASSWORD_FILE_PATH = "#{ENV["HOME"]}/.pwd.yml"
|
|
||||||
ASCII_LOGO = "
|
|
||||||
██████╗ ███╗ ███╗███╗ ██╗ ██████╗
|
|
||||||
██╔══██╗████╗ ████║████╗ ██║██╔════╝
|
|
||||||
██████╔╝██╔████╔██║██╔██╗ ██║██║ ███╗
|
|
||||||
██╔═══╝ ██║╚██╔╝██║██║╚██╗██║██║ ██║
|
|
||||||
██║ ██║ ╚═╝ ██║██║ ╚████║╚██████╔╝
|
|
||||||
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝
|
|
||||||
"
|
|
||||||
|
|
||||||
# program options
|
|
||||||
begin
|
|
||||||
OptionParser.parse do |parser|
|
|
||||||
parser.banner = "The very simple password manager for humans\n"
|
|
||||||
|
|
||||||
parser.on "-v", "--version", "Show version" do
|
|
||||||
puts ASCII_LOGO.colorize(:yellow)
|
|
||||||
puts "The very simple password manager for humans.".colorize(:yellow)
|
|
||||||
print "Version ".colorize(:yellow)
|
|
||||||
puts VERSION.colorize(:red).mode(:bold)
|
|
||||||
print "\nURL to full change log: ".colorize(:yellow)
|
|
||||||
puts "https://git.a2s.su/iiiypuk/pmng/raw/branch/master/HISTORY.md".colorize(:green).mode(:bold)
|
|
||||||
|
|
||||||
exit(0)
|
|
||||||
end
|
|
||||||
parser.on "-h", "--help", "Show help" do
|
|
||||||
puts parser
|
|
||||||
|
|
||||||
exit(0)
|
|
||||||
end
|
|
||||||
parser.on "-g", "--generate-password", "Generate password" do
|
|
||||||
puts Random::Secure.urlsafe_base64(16, padding: false).colorize(:black).back(:white)
|
|
||||||
puts Random::Secure.urlsafe_base64(16, padding: false).colorize(:white).back(:blue)
|
|
||||||
puts Random::Secure.urlsafe_base64(16, padding: false).colorize(:white).back(:red)
|
|
||||||
|
|
||||||
exit(0)
|
|
||||||
end
|
|
||||||
parser.on "-t", "--unixtime", "Return local timestamp" do
|
|
||||||
puts Time.local.to_unix.colorize(:yellow).mode(:bold)
|
|
||||||
|
|
||||||
exit(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue ex
|
|
||||||
puts ex.message, ""
|
|
||||||
end
|
|
||||||
|
|
||||||
# check password file exists
|
|
||||||
if File.exists?(PASSWORD_FILE_PATH)
|
|
||||||
yaml = File.open(PASSWORD_FILE_PATH) do |file|
|
yaml = File.open(PASSWORD_FILE_PATH) do |file|
|
||||||
YAML.parse(file)
|
YAML.parse(file)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
print "~/.pwd.yml".colorize(:red).mode(:bold)
|
print "~/.pwd.yml".colorize(:red).mode(:bold)
|
||||||
puts ": No such file"
|
puts ": No such file"
|
||||||
|
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
# check file ppermissions
|
# check file ppermissions
|
||||||
password_file_permissions = File.info(PASSWORD_FILE_PATH).permissions.to_s
|
password_file_permissions = File.info(PASSWORD_FILE_PATH).permissions.to_s
|
||||||
|
|
||||||
if /\d{3}/.match(password_file_permissions).try &.[0] != "600"
|
if /\d{3}/.match(password_file_permissions).try &.[0] != "600"
|
||||||
puts "Password file permissions is not RW for you.".colorize(:red)
|
puts "Password file permissions is not RW for you.".colorize(:red)
|
||||||
print "Execute: ".colorize(:yellow)
|
print "Execute: ".colorize(:yellow)
|
||||||
print "(chmod 600 ~/.pwd.yml) ".colorize(:green).mode(:bold)
|
print "(chmod 600 ~/.pwd.yml) ".colorize(:green).mode(:bold)
|
||||||
puts "for fix.".colorize(:yellow)
|
puts "for fix.".colorize(:yellow)
|
||||||
|
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
# fill passwords array
|
# fill passwords array
|
||||||
passwords_array = [] of Password
|
passwords_array = [] of Password
|
||||||
count = 0
|
count = 0
|
||||||
while count < yaml.size
|
while count < yaml.size
|
||||||
passwords_array << Password.from_yaml(yaml[count].to_yaml)
|
passwords_array << Password.from_yaml(yaml[count].to_yaml)
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
# pmng title
|
# pmng title
|
||||||
system "clear"
|
system "clear"
|
||||||
puts ASCII_LOGO.colorize(:yellow)
|
puts ASCII_LOGO.colorize(:yellow)
|
||||||
puts "The very simple password manager for humans".colorize(:yellow).mode(:bold)
|
puts "The very simple password manager for humans".colorize(:yellow).mode(:bold)
|
||||||
puts "-------------------------------------------".colorize(:yellow).mode(:bold)
|
puts "-------------------------------------------".colorize(:yellow).mode(:bold)
|
||||||
|
|
||||||
# main loop
|
statistics = Functions::Statistics.new(passwords_array)
|
||||||
loop = true
|
|
||||||
while loop
|
# main loop
|
||||||
|
loop = true
|
||||||
|
while loop
|
||||||
# shell prompt
|
# shell prompt
|
||||||
print "Enter URL (".colorize(:white).mode(:bold)
|
print "Enter URL (".colorize(:white).mode(:bold)
|
||||||
print ":h".colorize(:red).mode(:bold)
|
print ":h".colorize(:red).mode(:bold)
|
||||||
@@ -119,7 +57,12 @@ while loop
|
|||||||
print " for exit)\n".colorize(:white).mode(:bold)
|
print " for exit)\n".colorize(:white).mode(:bold)
|
||||||
print "> ".colorize(:green).mode(:bold)
|
print "> ".colorize(:green).mode(:bold)
|
||||||
|
|
||||||
password_string = gets
|
begin
|
||||||
|
STDIN.read_timeout = USER_INPUT_TIMEOUT
|
||||||
|
password_string = STDIN.gets
|
||||||
|
rescue IO::TimeoutError
|
||||||
|
loop = false
|
||||||
|
end
|
||||||
|
|
||||||
if password_string.to_s == ":q"
|
if password_string.to_s == ":q"
|
||||||
# if ':q' to close program
|
# if ':q' to close program
|
||||||
@@ -137,24 +80,13 @@ while loop
|
|||||||
elsif password_string.to_s == ":s"
|
elsif password_string.to_s == ":s"
|
||||||
# if ':s' to view Statistics
|
# if ':s' to view Statistics
|
||||||
system "clear"
|
system "clear"
|
||||||
|
|
||||||
puts "Statistics\n----------".colorize(:yellow).mode(:bold)
|
puts "Statistics\n----------".colorize(:yellow).mode(:bold)
|
||||||
|
|
||||||
print "All elements: ".colorize(:yellow).mode(:bold)
|
print "All elements: ".colorize(:yellow).mode(:bold)
|
||||||
puts passwords_array.size
|
puts statistics.size
|
||||||
|
|
||||||
print "Passwords outdated: ".colorize(:red).mode(:bold)
|
print "Passwords outdated: ".colorize(:red).mode(:bold)
|
||||||
outdated_count = 0
|
puts statistics.outdated
|
||||||
current_time = Time.local.to_unix
|
|
||||||
a = [] of String
|
|
||||||
passwords_array.each do |item|
|
|
||||||
if item.update + (2629743 * 3) < current_time # 2629743 * 3 -- 3 month
|
|
||||||
outdated_count += 1
|
|
||||||
a << item.url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
puts outdated_count
|
|
||||||
else
|
else
|
||||||
# list search password
|
# list search password
|
||||||
system "clear"
|
system "clear"
|
||||||
@@ -163,33 +95,7 @@ while loop
|
|||||||
|
|
||||||
passwords_array.each do |item|
|
passwords_array.each do |item|
|
||||||
if item.url.includes?(password_string.to_s)
|
if item.url.includes?(password_string.to_s)
|
||||||
print "🌐 "
|
Functions.show_item(item)
|
||||||
puts item.url.colorize(:magenta).mode(:bold).mode(:underline)
|
|
||||||
|
|
||||||
if !item.email.blank?
|
|
||||||
print "📧 "
|
|
||||||
puts item.email.colorize(:red)
|
|
||||||
end
|
|
||||||
|
|
||||||
if !item.login.blank?
|
|
||||||
print "🗿 "
|
|
||||||
puts item.login.colorize(:yellow)
|
|
||||||
end
|
|
||||||
|
|
||||||
if !item.password.blank?
|
|
||||||
print "🔐 "
|
|
||||||
puts item.password.colorize(:red).back(:red)
|
|
||||||
end
|
|
||||||
|
|
||||||
if !item.desc.blank?
|
|
||||||
print "📄 "
|
|
||||||
puts item.desc.colorize(:cyan)
|
|
||||||
end
|
|
||||||
|
|
||||||
if !item.profile_url.blank?
|
|
||||||
print "👦 "
|
|
||||||
puts item.profile_url.colorize(:green)
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "-----".colorize(:dark_gray).mode(:bold)
|
puts "-----".colorize(:dark_gray).mode(:bold)
|
||||||
|
|
||||||
@@ -205,7 +111,8 @@ while loop
|
|||||||
end
|
end
|
||||||
|
|
||||||
puts
|
puts
|
||||||
end
|
end
|
||||||
|
|
||||||
system "clear"
|
system "clear"
|
||||||
puts "Bye! 👋"
|
puts "Bye! 👋"
|
||||||
|
end
|
||||||
|
35
src/pmng/agrv_options.cr
Normal file
35
src/pmng/agrv_options.cr
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
begin
|
||||||
|
OptionParser.parse do |parser|
|
||||||
|
parser.banner = "The very simple password manager for humans\n"
|
||||||
|
|
||||||
|
parser.on "-v", "--version", "Show version" do
|
||||||
|
puts ASCII_LOGO.colorize(:yellow)
|
||||||
|
puts "The very simple password manager for humans.".colorize(:yellow)
|
||||||
|
print "Version ".colorize(:yellow)
|
||||||
|
puts VERSION.colorize(:red).mode(:bold)
|
||||||
|
puts "\nURL to full changes log: ".colorize(:yellow)
|
||||||
|
puts "https://git.a2s.su/iiiypuk/pmng/raw/branch/master/HISTORY.md".colorize(:green).mode(:bold)
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
end
|
||||||
|
parser.on "-h", "--help", "Show help" do
|
||||||
|
puts parser
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
end
|
||||||
|
parser.on "-g", "--generate-password", "Generate password" do
|
||||||
|
puts Random::Secure.urlsafe_base64(16, padding: false).colorize(:black).back(:white)
|
||||||
|
puts Random::Secure.urlsafe_base64(16, padding: false).colorize(:white).back(:blue)
|
||||||
|
puts Random::Secure.urlsafe_base64(16, padding: false).colorize(:white).back(:red)
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
end
|
||||||
|
parser.on "-t", "--unixtime", "Return local timestamp" do
|
||||||
|
puts Time.local.to_unix.colorize(:yellow).mode(:bold)
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue ex
|
||||||
|
puts ex.message, ""
|
||||||
|
end
|
13
src/pmng/app_settings.cr
Normal file
13
src/pmng/app_settings.cr
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
def password_file
|
||||||
|
begin
|
||||||
|
file_path = "#{ENV["PMNG_PWD_FILE"]}"
|
||||||
|
file_path.to_s
|
||||||
|
rescue KeyError
|
||||||
|
file_path = "#{ENV["HOME"]}/.pwd.yml"
|
||||||
|
file_path.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
VERSION = {{ `shards version "#{__DIR__}"`.chomp.stringify }}
|
||||||
|
PASSWORD_FILE_PATH = password_file()
|
||||||
|
USER_INPUT_TIMEOUT = 60
|
8
src/pmng/ascii_logo.cr
Normal file
8
src/pmng/ascii_logo.cr
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
ASCII_LOGO = "
|
||||||
|
██████╗ ███╗ ███╗███╗ ██╗ ██████╗
|
||||||
|
██╔══██╗████╗ ████║████╗ ██║██╔════╝
|
||||||
|
██████╔╝██╔████╔██║██╔██╗ ██║██║ ███╗
|
||||||
|
██╔═══╝ ██║╚██╔╝██║██║╚██╗██║██║ ██║
|
||||||
|
██║ ██║ ╚═╝ ██║██║ ╚████║╚██████╔╝
|
||||||
|
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ #{VERSION}
|
||||||
|
"
|
33
src/pmng/functions/item.cr
Normal file
33
src/pmng/functions/item.cr
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module Pmng::Functions
|
||||||
|
extend self
|
||||||
|
|
||||||
|
def show_item(password)
|
||||||
|
print "🌐 "
|
||||||
|
puts password.url.colorize(:magenta).mode(:bold).mode(:underline)
|
||||||
|
|
||||||
|
if !password.email.blank?
|
||||||
|
print "📧 "
|
||||||
|
puts password.email.colorize(:red)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !password.login.blank?
|
||||||
|
print "🗿 "
|
||||||
|
puts password.login.colorize(:yellow)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !password.password.blank?
|
||||||
|
print "🔐 "
|
||||||
|
puts password.password.colorize(:red).back(:red)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !password.desc.blank?
|
||||||
|
print "📄 "
|
||||||
|
puts password.desc.colorize(:cyan)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !password.profile_url.blank?
|
||||||
|
print "👦 "
|
||||||
|
puts password.profile_url.colorize(:green)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
33
src/pmng/functions/statistics.cr
Normal file
33
src/pmng/functions/statistics.cr
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module Pmng::Functions
|
||||||
|
extend self
|
||||||
|
|
||||||
|
class Statistics
|
||||||
|
getter passwords : Array(Password)
|
||||||
|
getter outdated : Int32
|
||||||
|
getter size : Int32
|
||||||
|
|
||||||
|
def initialize(@passwords)
|
||||||
|
@size = passwords.size
|
||||||
|
|
||||||
|
outdated_count = 0
|
||||||
|
current_time = Time.local.to_unix
|
||||||
|
a = [] of String
|
||||||
|
@passwords.each do |item|
|
||||||
|
if item.update + (2629743 * 3) < current_time # 2629743 * 3 -- 3 month
|
||||||
|
outdated_count += 1
|
||||||
|
a << item.url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@outdated = outdated_count
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@size
|
||||||
|
end
|
||||||
|
|
||||||
|
def outdated
|
||||||
|
@outdated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
18
src/pmng/password_class.cr
Normal file
18
src/pmng/password_class.cr
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class Password
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
@[YAML::Field(key: "url")]
|
||||||
|
property url : String
|
||||||
|
@[YAML::Field(key: "email")]
|
||||||
|
property email : String
|
||||||
|
@[YAML::Field(key: "login")]
|
||||||
|
property login : String
|
||||||
|
@[YAML::Field(key: "password")]
|
||||||
|
property password : String
|
||||||
|
@[YAML::Field(key: "desc")]
|
||||||
|
property desc : String
|
||||||
|
@[YAML::Field(key: "profile_url")]
|
||||||
|
property profile_url : String
|
||||||
|
@[YAML::Field(key: "update")]
|
||||||
|
property update : Int32
|
||||||
|
end
|
Reference in New Issue
Block a user