postdb 0.1.5

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