postdb 0.1.5

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7eb8c10674d608a4ebe84dc2c0a3c324235d4550
4
+ data.tar.gz: a932e7b6771517dfb39e57960ad49ec74fbfa93b
5
+ SHA512:
6
+ metadata.gz: 075eb7656fc26bef5b300e9beea11b1964fa91412c900959ab12224a82592de199e272bdf9f2e685e44ad7f1b468e2bcd7e7c2b8de02fae0cb210b43e838faa1
7
+ data.tar.gz: 97aae2ed0cef5e610e489ee38fb1bd754ec202f4686f54155958d54d14cc483195849bcab0c3141219d37a72307750792625885a748716f158c524dcd85c0838
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :sqlite do
6
+ gem 'dm-sqlite-adapter', '~> 1.2'
7
+ end
8
+
9
+ group :mysql do
10
+ gem 'dm-mysql-adapter', '~> 1.2'
11
+ end
12
+
13
+ group :postgres do
14
+ gem 'dm-postgres-adapter', '~> 1.2'
15
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'postdb'
5
+
6
+ require 'irb'
7
+
8
+ IRB.start
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'postdb/cli'
4
+
5
+ PostDB::CLI::Main.start(ARGV)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,10 @@
1
+ class CreateDomains < ActiveRecord::Migration
2
+ def change
3
+ create_table :virtual_domains do |t|
4
+ t.string :name, null: false
5
+ t.binary :dkim, null: false
6
+
7
+ t.timestamps(null: false)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :virtual_users do |t|
4
+ t.integer :domain_id, null: false
5
+ t.string :email, null: false
6
+ t.string :crypted_password, null: false
7
+
8
+ t.timestamps(null: false)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class CreateAliases < ActiveRecord::Migration
2
+ def change
3
+ create_table :virtual_aliases do |t|
4
+ t.integer :domain_id, null: false
5
+ t.integer :user_id, null: true
6
+ t.string :source, null: false
7
+ t.string :destination, null: false
8
+
9
+ t.timestamps(null: false)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ # The PostDB main module
2
+ #
3
+ module PostDB
4
+ class << self
5
+ # Require dependencies
6
+ #
7
+ # Example:
8
+ # >> PostDB.require!
9
+ # => nil
10
+ #
11
+ def require!
12
+ require 'cgi'
13
+ require 'openssl'
14
+ require 'fileutils'
15
+ require 'active_support'
16
+ require 'active_record'
17
+ require 'attr_password'
18
+
19
+ require 'postdb/constants'
20
+ require 'postdb/errors'
21
+ require 'postdb/helpers'
22
+ require 'postdb/configuration'
23
+ require 'postdb/database'
24
+ require 'postdb/mail_location'
25
+ require 'postdb/mail'
26
+ require 'postdb/dkim'
27
+ require 'postdb/domain'
28
+ require 'postdb/user'
29
+ require 'postdb/alias'
30
+ end
31
+
32
+ # Setup the gem
33
+ #
34
+ # Arguments:
35
+ # path: (String) The path to the configuration file
36
+ #
37
+ # Example:
38
+ # >> PostDB.setup(path)
39
+ # => true
40
+ #
41
+ def setup(path)
42
+ PostDB::Configuration.load_file(path)
43
+
44
+ PostDB::Database.setup_with_configuration!
45
+ PostDB::Mail.setup_with_configuration!
46
+ PostDB::DKIM.setup_with_configuration!
47
+
48
+ true
49
+ end
50
+ end
51
+ end
52
+
53
+ # Require dependencies
54
+ #
55
+ PostDB.require!
@@ -0,0 +1,24 @@
1
+ module PostDB
2
+ class Alias < ActiveRecord::Base
3
+ self.table_name = 'virtual_aliases'
4
+
5
+ belongs_to :domain
6
+ belongs_to :user
7
+
8
+ validates :source, presence: true
9
+ validates :destination, presence: true
10
+
11
+ validate do |aliaz|
12
+ alias_count = self.class.where(
13
+ source: aliaz.source,
14
+ destination: aliaz.destination
15
+ ).where.not(
16
+ id: aliaz.id
17
+ ).count
18
+
19
+ next unless alias_count > 0
20
+
21
+ errors.add(:source_and_destination, "must be unique")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ # The PostDB main module
2
+ #
3
+ module PostDB
4
+ # The CLI module
5
+ #
6
+ module CLI
7
+ class << self
8
+ # Require dependencies
9
+ #
10
+ # Example:
11
+ # >> PostDB.require!
12
+ # => nil
13
+ #
14
+ def require!
15
+ require 'yaml'
16
+ require 'thor'
17
+ require 'tty-prompt'
18
+ require 'tty-table'
19
+ require 'postdb'
20
+
21
+ require 'postdb/cli/helper'
22
+ require 'postdb/cli/database'
23
+ require 'postdb/cli/domains/dkim'
24
+ require 'postdb/cli/domains'
25
+ require 'postdb/cli/users'
26
+ require 'postdb/cli/aliases'
27
+ require 'postdb/cli/main'
28
+ end
29
+
30
+ # Ensure superuser
31
+ #
32
+ # Example:
33
+ # >> PostDB::CLI.ensure_superuser!
34
+ # => nil
35
+ #
36
+ def ensure_superuser!
37
+ return nil if 0 == Process.uid
38
+
39
+ prompt = TTY::Prompt.new
40
+ prompt.error("PostDB requires superuser privileges to run!")
41
+
42
+ exit 1
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ # Require dependencies
49
+ #
50
+ PostDB::CLI.require!
51
+
52
+ # Ensure superuser privileges
53
+ #
54
+ PostDB::CLI.ensure_superuser!
@@ -0,0 +1,123 @@
1
+ module PostDB
2
+ module CLI
3
+ class Aliases < Thor
4
+ SOURCE_REGEX = /^([A-Za-z0-9\-\_\.\+]+)?@[A-Za-z0-9\-]+\.[A-Za-z0-9\-\.]+$/
5
+
6
+ no_tasks do
7
+ include PostDB::CLI::Helper
8
+ end
9
+
10
+ desc "list DOMAIN", "List all aliases"
11
+ def list(domain = nil)
12
+ domains = PostDB::Domain.where(**(domain ? { name: domain } : {}))
13
+
14
+ if domains.empty?
15
+ if domain
16
+ exit_with_warning("The domain '#{domain}' could not be found.")
17
+ else
18
+ exit_with_warning("There don't appear to be any domains on this system.")
19
+ end
20
+ end
21
+
22
+ domains = domains.to_a
23
+ domains.sort! { |a, b| a.name <=> b.name }
24
+
25
+ domains.each_with_index do |domain, index|
26
+ aliases = domain.forwarding_aliases.sort { |a, b| a.source <=> b.source }
27
+
28
+ if aliases.empty?
29
+ puts TTY::Table.new(
30
+ header: [domain.name].pad(' '),
31
+ rows: [['No Aliases'].pad(' ')]
32
+ ).render(:ascii)
33
+ else
34
+ puts TTY::Table.new(
35
+ header: [" " + domain.name + " \n Source: ", "\n Destination: "],
36
+ rows: aliases.map { |a| [a.source, a.destination].pad(' ') }
37
+ ).render(:ascii, multiline: true)
38
+ end
39
+
40
+ new_line unless (index + 1) == domains.count
41
+ end
42
+ end
43
+
44
+ desc "add SOURCE DESTINATION", "Add an alias"
45
+ def add(source = nil, destination = nil)
46
+ unless source
47
+ source = prompt.ask("Source:") do |q|
48
+ q.required(true)
49
+ q.validate(SOURCE_REGEX)
50
+ end
51
+ end
52
+
53
+ unless destination
54
+ destination = prompt.ask("Destination:") do |q|
55
+ q.required(true)
56
+ q.validate(:email)
57
+ end
58
+ end
59
+
60
+ source = source.downcase
61
+ destination = destination.downcase
62
+
63
+ if source == destination
64
+ exit_with_error("The alias '#{source} -> #{destination}' is managed automatically by PostDB.")
65
+ end
66
+
67
+ if PostDB::Alias.where(source: source, destination: destination).count > 0
68
+ exit_with_warning("The alias '#{source} -> #{destination}' has already been added.")
69
+ end
70
+
71
+ domain_name = source.split('@')[1]
72
+
73
+ unless domain = PostDB::Domain.where(name: domain_name).first
74
+ exit_with_error("The domain '#{domain_name}' is not available.")
75
+ end
76
+
77
+ PostDB::Alias.create(domain: domain, source: source, destination: destination)
78
+
79
+ prompt.ok("The alias '#{source} -> #{destination}' has been added.")
80
+ end
81
+
82
+ desc "remove SOURCE DESTINATION", "Remove an alias"
83
+ option :force, type: :boolean, default: false
84
+ def remove(source = nil, destination = nil)
85
+ unless source && destination
86
+ aliases = PostDB::Alias.all.select { |a| a.source != a.destination }
87
+
88
+ if aliases.empty?
89
+ exit_with_warning("There don't appear to be any aliases on this system.")
90
+ end
91
+
92
+ a = prompt.select("Alias:", aliases.map { |a| ["#{a.source} -> #{a.destination}", a] }.to_h)
93
+
94
+ source = a.source
95
+ destination = a.destination
96
+ end
97
+
98
+ source = source.downcase
99
+ destination = destination.downcase
100
+
101
+ if source == destination
102
+ exit_with_error("The alias '#{source} -> #{destination}' is managed automatically by PostDB.")
103
+ end
104
+
105
+ aliases = PostDB::Alias.where(source: source, destination: destination)
106
+
107
+ if aliases.empty?
108
+ exit_with_warning("The alias '#{source} -> #{destination}' could not be found.")
109
+ end
110
+
111
+ unless options[:force]
112
+ confirm_action!("Remove the alias '#{source} -> #{destination}'?", "'#{source} -> #{destination}' left untouched.")
113
+ end
114
+
115
+ aliases.each(&:destroy)
116
+
117
+ prompt.ok("The alias '#{source} -> #{destination}' has been removed.")
118
+ end
119
+
120
+ default_task :list
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,39 @@
1
+ module PostDB
2
+ module CLI
3
+ class Database < Thor
4
+ no_tasks do
5
+ include PostDB::CLI::Helper
6
+
7
+ # Get the ActiveRecord::Base connection
8
+ #
9
+ # Example:
10
+ # >> connection
11
+ # => ?
12
+ #
13
+ def connection
14
+ ActiveRecord::Base.connection
15
+ end
16
+
17
+ # Get the database configuration
18
+ #
19
+ # Example:
20
+ # >> configuration
21
+ # => { adapter: "mysql", host: "127.0.0.1", username: "mail", password: "...", database: "mail" }
22
+ #
23
+ def configuration
24
+ PostDB::Configuration[:database]
25
+ end
26
+ end
27
+
28
+ desc "migrate VERSION", "Migrate the database"
29
+ def migrate(version = nil)
30
+ # Get the path to the migrations directory
31
+ migrations = File.join('..', '..', '..', '..', 'db', 'migrate')
32
+ migrations = File.expand_path(migrations, __FILE__)
33
+
34
+ # Run the migrations
35
+ ActiveRecord::Migrator.migrate(migrations, version)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ module PostDB
2
+ module CLI
3
+ class Domains < Thor
4
+ no_tasks do
5
+ include PostDB::CLI::Helper
6
+ end
7
+
8
+ desc "list", "List all domains"
9
+ def list
10
+ domains = PostDB::Domain.all
11
+
12
+ if domains.empty?
13
+ exit_with_warning("There don't appear to be any domains on this system.")
14
+ end
15
+
16
+ domains = domains.to_a
17
+ domains.sort! { |a, b| a.name <=> b.name }
18
+
19
+ puts TTY::Table.new(
20
+ header: ["Domain Name", "Total Users", "Total Aliases"].pad(' '),
21
+ rows: domains.map { |d| [d.name, d.users.count, d.forwarding_aliases.count].pad(' ') }
22
+ ).render(:ascii, multiline: true)
23
+ end
24
+
25
+ desc "add DOMAIN", "Add a domain"
26
+ def add(domain_name = nil)
27
+ unless domain_name
28
+ domain_name = prompt.ask("Domain:") do |q|
29
+ q.required(true)
30
+ end
31
+ end
32
+
33
+ domain_name = domain_name.downcase
34
+
35
+ if PostDB::Domain.where(name: domain_name).count > 0
36
+ exit_with_warning("The domain '#{domain_name}' has already been added.")
37
+ end
38
+
39
+ domain = PostDB::Domain.new
40
+ domain.name = domain_name
41
+
42
+ if domain.save
43
+ prompt.ok("The domain '#{domain_name}' has been added.")
44
+ else
45
+ errors = domain.errors.full_messages.map { |m| " #{m}" }
46
+ exit_with_error("The domain '#{domain_name}' couldn't be added:", *errors)
47
+ end
48
+ end
49
+
50
+ desc "remove DOMAIN", "Remove a domain"
51
+ option :force, type: :boolean, default: false
52
+ def remove(domain_name = nil)
53
+ unless domain_name
54
+ domains = PostDB::Domain.all
55
+
56
+ if domains.empty?
57
+ exit_with_warning("There don't appear to be any domains on this system.")
58
+ end
59
+
60
+ domains = domains.to_a
61
+ domains.sort! { |a, b| a.name <=> b.name }
62
+
63
+ domain_name = prompt.select("Domain:", domains.map(&:name))
64
+ end
65
+
66
+ domain_name = domain_name.downcase
67
+
68
+ domains = PostDB::Domain.where(name: domain_name)
69
+
70
+ if domains.empty?
71
+ exit_with_warning("The domain '#{domain_name}' could not be found.")
72
+ end
73
+
74
+ unless options[:force]
75
+ confirm_action!("Remove the domain '#{domain_name}'?", "'#{domain_name}' left untouched.")
76
+ end
77
+
78
+ domains.each(&:destroy)
79
+
80
+ prompt.ok("The domain '#{domain_name}' has been removed.")
81
+ end
82
+
83
+ desc "dkim SUBCOMMAND ...ARGS", "Manage DKIM keys"
84
+ subcommand "dkim", PostDB::CLI::Domains::DKIM
85
+
86
+ default_task :list
87
+ end
88
+ end
89
+ end