yodel 0.0.1
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.
- data/.document +5 -0
- data/.gitignore +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +1 -0
- data/README.rdoc +20 -0
- data/Rakefile +1 -0
- data/bin/yodel +4 -0
- data/lib/yodel.rb +22 -0
- data/lib/yodel/application/application.rb +44 -0
- data/lib/yodel/application/extension.rb +59 -0
- data/lib/yodel/application/request_handler.rb +48 -0
- data/lib/yodel/application/yodel.rb +25 -0
- data/lib/yodel/command/command.rb +94 -0
- data/lib/yodel/command/deploy.rb +67 -0
- data/lib/yodel/command/dns_server.rb +16 -0
- data/lib/yodel/command/installer.rb +229 -0
- data/lib/yodel/config/config.rb +30 -0
- data/lib/yodel/config/environment.rb +16 -0
- data/lib/yodel/config/yodel.rb +21 -0
- data/lib/yodel/exceptions/destroyed_record.rb +2 -0
- data/lib/yodel/exceptions/domain_not_found.rb +16 -0
- data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
- data/lib/yodel/exceptions/exceptions.rb +3 -0
- data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
- data/lib/yodel/exceptions/invalid_field.rb +2 -0
- data/lib/yodel/exceptions/invalid_index.rb +2 -0
- data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
- data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
- data/lib/yodel/exceptions/layout_not_found.rb +2 -0
- data/lib/yodel/exceptions/mass_assignment.rb +2 -0
- data/lib/yodel/exceptions/missing_migration.rb +2 -0
- data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
- data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
- data/lib/yodel/exceptions/unauthorised.rb +2 -0
- data/lib/yodel/exceptions/unknown_field.rb +2 -0
- data/lib/yodel/middleware/development_server.rb +180 -0
- data/lib/yodel/middleware/error_pages.rb +72 -0
- data/lib/yodel/middleware/public_assets.rb +78 -0
- data/lib/yodel/middleware/request.rb +16 -0
- data/lib/yodel/middleware/site_detector.rb +22 -0
- data/lib/yodel/mime_types/default_mime_set.rb +28 -0
- data/lib/yodel/mime_types/mime_type.rb +68 -0
- data/lib/yodel/mime_types/mime_type_set.rb +41 -0
- data/lib/yodel/mime_types/mime_types.rb +6 -0
- data/lib/yodel/mime_types/yodel.rb +15 -0
- data/lib/yodel/models/api/api.rb +1 -0
- data/lib/yodel/models/api/api_call.rb +87 -0
- data/lib/yodel/models/core/associations/association.rb +37 -0
- data/lib/yodel/models/core/associations/associations.rb +22 -0
- data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
- data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
- data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
- data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
- data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
- data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
- data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
- data/lib/yodel/models/core/associations/record_association.rb +38 -0
- data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
- data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
- data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
- data/lib/yodel/models/core/attachments/attachment.rb +73 -0
- data/lib/yodel/models/core/attachments/image.rb +38 -0
- data/lib/yodel/models/core/core.rb +15 -0
- data/lib/yodel/models/core/fields/alias_field.rb +32 -0
- data/lib/yodel/models/core/fields/array_field.rb +64 -0
- data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
- data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
- data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
- data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
- data/lib/yodel/models/core/fields/color_field.rb +4 -0
- data/lib/yodel/models/core/fields/date_field.rb +35 -0
- data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
- data/lib/yodel/models/core/fields/email_field.rb +10 -0
- data/lib/yodel/models/core/fields/enum_field.rb +33 -0
- data/lib/yodel/models/core/fields/field.rb +154 -0
- data/lib/yodel/models/core/fields/fields.rb +29 -0
- data/lib/yodel/models/core/fields/fields_field.rb +31 -0
- data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
- data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
- data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
- data/lib/yodel/models/core/fields/function_field.rb +28 -0
- data/lib/yodel/models/core/fields/hash_field.rb +54 -0
- data/lib/yodel/models/core/fields/html_field.rb +15 -0
- data/lib/yodel/models/core/fields/image_field.rb +11 -0
- data/lib/yodel/models/core/fields/integer_field.rb +25 -0
- data/lib/yodel/models/core/fields/password_field.rb +21 -0
- data/lib/yodel/models/core/fields/self_field.rb +27 -0
- data/lib/yodel/models/core/fields/string_field.rb +15 -0
- data/lib/yodel/models/core/fields/tags_field.rb +7 -0
- data/lib/yodel/models/core/fields/text_field.rb +7 -0
- data/lib/yodel/models/core/fields/time_field.rb +36 -0
- data/lib/yodel/models/core/functions/function.rb +471 -0
- data/lib/yodel/models/core/functions/functions.rb +2 -0
- data/lib/yodel/models/core/functions/trigger.rb +14 -0
- data/lib/yodel/models/core/log/log.rb +33 -0
- data/lib/yodel/models/core/log/log_entry.rb +12 -0
- data/lib/yodel/models/core/model/abstract_model.rb +59 -0
- data/lib/yodel/models/core/model/model.rb +460 -0
- data/lib/yodel/models/core/model/mongo_model.rb +25 -0
- data/lib/yodel/models/core/model/site_model.rb +17 -0
- data/lib/yodel/models/core/mongo/mongo.rb +3 -0
- data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
- data/lib/yodel/models/core/mongo/query.rb +68 -0
- data/lib/yodel/models/core/mongo/record_index.rb +89 -0
- data/lib/yodel/models/core/record/abstract_record.rb +411 -0
- data/lib/yodel/models/core/record/embedded_record.rb +47 -0
- data/lib/yodel/models/core/record/mongo_record.rb +83 -0
- data/lib/yodel/models/core/record/record.rb +386 -0
- data/lib/yodel/models/core/record/section.rb +21 -0
- data/lib/yodel/models/core/record/site_record.rb +31 -0
- data/lib/yodel/models/core/site/migration.rb +52 -0
- data/lib/yodel/models/core/site/remote.rb +61 -0
- data/lib/yodel/models/core/site/site.rb +202 -0
- data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
- data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
- data/lib/yodel/models/core/validations/errors.rb +51 -0
- data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
- data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
- data/lib/yodel/models/core/validations/format_validation.rb +10 -0
- data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
- data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
- data/lib/yodel/models/core/validations/length_validation.rb +28 -0
- data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
- data/lib/yodel/models/core/validations/required_validation.rb +9 -0
- data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
- data/lib/yodel/models/core/validations/validation.rb +39 -0
- data/lib/yodel/models/core/validations/validations.rb +15 -0
- data/lib/yodel/models/email/email.rb +79 -0
- data/lib/yodel/models/migrations/01_record_model.rb +29 -0
- data/lib/yodel/models/migrations/02_page_model.rb +45 -0
- data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
- data/lib/yodel/models/migrations/04_group_model.rb +61 -0
- data/lib/yodel/models/migrations/05_user_model.rb +24 -0
- data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
- data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
- data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
- data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
- data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
- data/lib/yodel/models/migrations/11_email_model.rb +28 -0
- data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
- data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
- data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
- data/lib/yodel/models/models.rb +8 -0
- data/lib/yodel/models/pages/form_builder.rb +379 -0
- data/lib/yodel/models/pages/html_decorator.rb +132 -0
- data/lib/yodel/models/pages/layout.rb +120 -0
- data/lib/yodel/models/pages/menu.rb +32 -0
- data/lib/yodel/models/pages/page.rb +378 -0
- data/lib/yodel/models/pages/pages.rb +7 -0
- data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
- data/lib/yodel/models/pages/redirect_page.rb +11 -0
- data/lib/yodel/models/search/search.rb +1 -0
- data/lib/yodel/models/search/search_page.rb +58 -0
- data/lib/yodel/models/security/facebook_login_page.rb +55 -0
- data/lib/yodel/models/security/group.rb +10 -0
- data/lib/yodel/models/security/guests_group.rb +5 -0
- data/lib/yodel/models/security/login_page.rb +20 -0
- data/lib/yodel/models/security/logout_page.rb +13 -0
- data/lib/yodel/models/security/noone_group.rb +5 -0
- data/lib/yodel/models/security/owner_group.rb +8 -0
- data/lib/yodel/models/security/password.rb +5 -0
- data/lib/yodel/models/security/password_reset_page.rb +47 -0
- data/lib/yodel/models/security/security.rb +10 -0
- data/lib/yodel/models/security/user.rb +33 -0
- data/lib/yodel/public/core/css/core.css +257 -0
- data/lib/yodel/public/core/css/reset.css +48 -0
- data/lib/yodel/public/core/images/cross.png +0 -0
- data/lib/yodel/public/core/images/spinner.gif +0 -0
- data/lib/yodel/public/core/images/tick.png +0 -0
- data/lib/yodel/public/core/images/yodel.png +0 -0
- data/lib/yodel/public/core/js/jquery.min.js +18 -0
- data/lib/yodel/public/core/js/json2.js +480 -0
- data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
- data/lib/yodel/request/authentication.rb +76 -0
- data/lib/yodel/request/flash.rb +28 -0
- data/lib/yodel/request/request.rb +4 -0
- data/lib/yodel/requires.rb +47 -0
- data/lib/yodel/task_queue/queue_daemon.rb +33 -0
- data/lib/yodel/task_queue/queue_worker.rb +32 -0
- data/lib/yodel/task_queue/stats_thread.rb +27 -0
- data/lib/yodel/task_queue/task.rb +62 -0
- data/lib/yodel/task_queue/task_queue.rb +40 -0
- data/lib/yodel/types/date.rb +5 -0
- data/lib/yodel/types/object_id.rb +11 -0
- data/lib/yodel/types/time.rb +5 -0
- data/lib/yodel/version.rb +3 -0
- data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
- data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
- data/system/etc/resolver/yodel +2 -0
- data/system/usr/local/bin/yodel_command_runner +2 -0
- data/system/usr/local/etc/yodel/development_settings.rb +28 -0
- data/system/usr/local/etc/yodel/production_settings.rb +27 -0
- data/system/var/log/yodel.log +0 -0
- data/test/helper.rb +18 -0
- data/test/test_yodel.rb +4 -0
- data/yodel.gemspec +47 -0
- metadata +501 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'rubydns'
|
|
2
|
+
|
|
3
|
+
class DNSServer
|
|
4
|
+
def self.start
|
|
5
|
+
@resolv = Resolv::DNS.new
|
|
6
|
+
RubyDNS::run_server(:listen => [[:udp, "0.0.0.0", Yodel.config.dns_port], [:tcp, "0.0.0.0", Yodel.config.dns_port]]) do
|
|
7
|
+
match(/yodel/) do |match_data, transaction|
|
|
8
|
+
transaction.respond!("127.0.0.1")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
otherwise do |transaction|
|
|
12
|
+
transaction.passthrough!(@resolv)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
require 'highline'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
require 'ember'
|
|
4
|
+
require 'etc'
|
|
5
|
+
|
|
6
|
+
class Installer
|
|
7
|
+
# ----------------------------------------
|
|
8
|
+
# Template variables
|
|
9
|
+
# ----------------------------------------
|
|
10
|
+
attr_reader :database_hostname, :database_port, :database_name,
|
|
11
|
+
:sites_root, :ruby_path, :web_port, :dns_port,
|
|
12
|
+
:user, :group, :public_directory
|
|
13
|
+
|
|
14
|
+
def default_sites_root
|
|
15
|
+
if `uname -a` =~ /Darwin/
|
|
16
|
+
File.expand_path('~/Sites')
|
|
17
|
+
else
|
|
18
|
+
'/var/www'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def default_ruby_path
|
|
23
|
+
if Config.ruby =~ /rubies\/(.*)\/bin/
|
|
24
|
+
`rvm wrapper #{$1} yodel`
|
|
25
|
+
`which yodel_ruby`.strip
|
|
26
|
+
else
|
|
27
|
+
Config.ruby
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def assign_default_user_and_group
|
|
32
|
+
pwnam = Etc.getpwnam(Etc.getlogin)
|
|
33
|
+
@user = pwnam.uid
|
|
34
|
+
@group = pwnam.gid
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def default_group
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
# assign default values; not all values have associated questions
|
|
42
|
+
# presented to the user depen
|
|
43
|
+
@database_hostname = 'localhost'
|
|
44
|
+
@database_port = 27017
|
|
45
|
+
@database_name = 'yodel'
|
|
46
|
+
@web_port = 80
|
|
47
|
+
@dns_port = 2828
|
|
48
|
+
@public_directory = '/var/www'
|
|
49
|
+
@sites_root = default_sites_root
|
|
50
|
+
@ruby_path = default_ruby_path
|
|
51
|
+
assign_default_user_and_group
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ----------------------------------------
|
|
56
|
+
# Helpers
|
|
57
|
+
# ----------------------------------------
|
|
58
|
+
def system_path
|
|
59
|
+
@system_path ||= File.join(File.dirname(__FILE__), '..', '..', '..', 'system')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def report(verb, noun)
|
|
63
|
+
@h.say "<%= color('#{verb}', GREEN) %>\t#{noun}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def escape_quotes(str)
|
|
67
|
+
str.gsub("'", "\\\\'")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def install(file, permissions='0644', file_name=nil)
|
|
71
|
+
if file_name
|
|
72
|
+
dest_path = File.join('/', File.dirname(file), file_name)
|
|
73
|
+
else
|
|
74
|
+
dest_path = File.join('/', file)
|
|
75
|
+
end
|
|
76
|
+
source_path = File.join(system_path, file)
|
|
77
|
+
temp_file = Tempfile.new('yodel')
|
|
78
|
+
report('installing', dest_path)
|
|
79
|
+
|
|
80
|
+
# render the file template
|
|
81
|
+
temp_file.write Ember::Template.new(IO.read(source_path), {source_file: source_path}).render(binding)
|
|
82
|
+
temp_file.close
|
|
83
|
+
|
|
84
|
+
# create parent directories, copy the rendered file and set permissions
|
|
85
|
+
`sudo mkdir -p #{File.dirname(dest_path)}`
|
|
86
|
+
`sudo cp #{temp_file.path} #{dest_path}`
|
|
87
|
+
`sudo chmod #{permissions} #{dest_path}`
|
|
88
|
+
|
|
89
|
+
# delete the temp file used for rendering
|
|
90
|
+
temp_file.unlink
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ----------------------------------------
|
|
95
|
+
# Q & A
|
|
96
|
+
# ----------------------------------------
|
|
97
|
+
def install_system_files
|
|
98
|
+
@h = h = HighLine.new
|
|
99
|
+
h.say "\n<%= color('Welcome to Yodel', BOLD) %>\n\n"
|
|
100
|
+
|
|
101
|
+
# environment
|
|
102
|
+
h.say "<%= color('Environment', BOLD) %>"
|
|
103
|
+
h.say "Yodel can run in development (local) mode or as a production server. Press"
|
|
104
|
+
h.say "enter to setup Yodel in development mode, or enter 'production' to setup"
|
|
105
|
+
h.say "a production server."
|
|
106
|
+
@environment = h.ask("Environment: ") do |q|
|
|
107
|
+
q.default = 'development'
|
|
108
|
+
q.in = %w{development production}
|
|
109
|
+
q.responses[:ask_on_error] = :question
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# local sites directory
|
|
113
|
+
h.say "\n<%= color('1/3', RED) %> <%= color('Sites Directory', BOLD) %>"
|
|
114
|
+
h.say "Yodel stores each of your sites in a single directory. You may place this directory"
|
|
115
|
+
h.say "anywhere, but all sites created by Yodel must be accessible from it. Press enter to"
|
|
116
|
+
h.say "accept the default directory, or enter a new path."
|
|
117
|
+
@sites_root = h.ask("Sites directory: ") do |q|
|
|
118
|
+
q.default = @sites_root
|
|
119
|
+
q.responses[:ask_on_error] = :question
|
|
120
|
+
end
|
|
121
|
+
@sites_root = escape_quotes(@sites_root)
|
|
122
|
+
|
|
123
|
+
# web server port
|
|
124
|
+
if @environment == 'development'
|
|
125
|
+
h.say "\n<%= color('2/3', RED) %> <%= color('Local Server', BOLD) %>"
|
|
126
|
+
h.say "By default the yodel server will run on port 80, meaning you can access yodel"
|
|
127
|
+
h.say "sites by visiting <http://sitename.yodel/>. If you already have Apache or"
|
|
128
|
+
h.say "another web server running on this port, enter a different port (such as 8080)"
|
|
129
|
+
@web_port = h.ask("Use port: ", Integer) do |q|
|
|
130
|
+
q.default = @web_port
|
|
131
|
+
q.responses[:ask_on_error] = :question
|
|
132
|
+
end
|
|
133
|
+
else
|
|
134
|
+
h.say "\n<%= color('2/3', RED) %> <%= color('Public Directory', BOLD) %>"
|
|
135
|
+
h.say "Yodel symlinks the public directory of sites served by the production server"
|
|
136
|
+
h.say "to a directory visible to the fronting web server. The domain of a site is"
|
|
137
|
+
h.say "used as the link name, meaning /var/www/domain.com could e.g link to"
|
|
138
|
+
h.say "/var/git/ID/public. This makes serving public assets from a fronting server"
|
|
139
|
+
h.say "possible by rewriting the request path to include the domain of the request."
|
|
140
|
+
@public_directory = h.ask("Public directory: ") do |q|
|
|
141
|
+
q.default = @public_directory
|
|
142
|
+
q.responses[:ask_on_error] = :question
|
|
143
|
+
end
|
|
144
|
+
@public_directory = escape_quotes(@public_directory)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
h.say "\n<%= color('3/3', RED) %> <%= color('Installing files', BOLD) %>"
|
|
148
|
+
h.say "Yodel will now install the necessary system files. You may be asked to enter"
|
|
149
|
+
h.say "your local user password.\n\n"
|
|
150
|
+
h.say "-----------------------------------------------------------------------------\n\n"
|
|
151
|
+
|
|
152
|
+
# install system files
|
|
153
|
+
if `uname -a` =~ /Darwin/
|
|
154
|
+
install_mac_files
|
|
155
|
+
else
|
|
156
|
+
install_linux_files
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# start yodel for environment installation
|
|
160
|
+
report('starting', 'yodel')
|
|
161
|
+
require '../../yodel'
|
|
162
|
+
Yodel.config.extensions_folder = $extensions_folder if $extensions_folder
|
|
163
|
+
Yodel.load_extensions
|
|
164
|
+
|
|
165
|
+
# install an environment support site
|
|
166
|
+
report('installing', "#{@environment} environment support site")
|
|
167
|
+
site = Site.new
|
|
168
|
+
site.name = "yodel"
|
|
169
|
+
site.domains = ['yodel', 'localhost', '127.0.0.1']
|
|
170
|
+
extension = Yodel.extensions["yodel_#{@environment}_environment"]
|
|
171
|
+
puts "Installation environment (#{@environment}) not found" and exit(1) if extension.nil?
|
|
172
|
+
site.root_directory = extension.lib_dir
|
|
173
|
+
site.save
|
|
174
|
+
Migration.run_migrations(site)
|
|
175
|
+
|
|
176
|
+
h.say "\n-----------------------------------------------------------------------------"
|
|
177
|
+
h.say "\n<%= color('Installation complete', BOLD) %>"
|
|
178
|
+
h.say "Visit http://yodel#{":#{@web_port}" if @web_port != 80}/ to setup accounts you have on a remote Yodel server."
|
|
179
|
+
h.say "You can create a new site by visiting <http://sitename.yodel#{":#{@web_port}" if @web_port != 80}/> and"
|
|
180
|
+
h.say "following the instructions\n\n"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ----------------------------------------
|
|
185
|
+
# OS X
|
|
186
|
+
# ----------------------------------------
|
|
187
|
+
def install_mac_files
|
|
188
|
+
install 'etc/resolver/yodel'
|
|
189
|
+
install 'Library/LaunchDaemons/com.yodelcms.dns.plist'
|
|
190
|
+
install 'Library/LaunchDaemons/com.yodelcms.server.plist'
|
|
191
|
+
install 'usr/local/bin/yodel_command_runner', '0777'
|
|
192
|
+
install 'var/log/yodel.log', '0666'
|
|
193
|
+
|
|
194
|
+
case @environment
|
|
195
|
+
when 'development'
|
|
196
|
+
install 'usr/local/etc/yodel/development_settings.rb', '0644', 'settings.rb'
|
|
197
|
+
when 'production'
|
|
198
|
+
install 'usr/local/etc/yodel/production_settings.rb', '0644', 'settings.rb'
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
report('starting', 'dns server')
|
|
202
|
+
if `sudo launchctl list` =~ /\d+.+com.yodelcms.dns$/
|
|
203
|
+
`sudo launchctl unload /Library/LaunchDaemons/com.yodelcms.dns.plist`
|
|
204
|
+
end
|
|
205
|
+
`sudo launchctl load /Library/LaunchDaemons/com.yodelcms.dns.plist`
|
|
206
|
+
|
|
207
|
+
report('starting', 'web server')
|
|
208
|
+
if `sudo launchctl list` =~ /\d+.+com.yodelcms.server$/
|
|
209
|
+
`sudo launchctl unload /Library/LaunchDaemons/com.yodelcms.server.plist`
|
|
210
|
+
end
|
|
211
|
+
`sudo launchctl load /Library/LaunchDaemons/com.yodelcms.server.plist`
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ----------------------------------------
|
|
216
|
+
# Linux
|
|
217
|
+
# ----------------------------------------
|
|
218
|
+
def install_linux_files
|
|
219
|
+
install 'usr/local/bin/yodel_command_runner', '0777'
|
|
220
|
+
install 'var/log/yodel.log', '0666'
|
|
221
|
+
|
|
222
|
+
case @environment
|
|
223
|
+
when 'development'
|
|
224
|
+
install 'usr/local/etc/yodel/development_settings.rb', '0644', 'settings.rb'
|
|
225
|
+
when 'production'
|
|
226
|
+
install 'usr/local/etc/yodel/production_settings.rb', '0644', 'settings.rb'
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Dir.chdir(File.dirname(__FILE__)) do
|
|
2
|
+
require './environment'
|
|
3
|
+
require './yodel'
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class YodelConfig
|
|
7
|
+
def initialize
|
|
8
|
+
@options = {
|
|
9
|
+
'yodel_migration_directory' => File.join(File.dirname(__FILE__), '..', 'models', 'migrations'),
|
|
10
|
+
'public_directories' => [File.join(File.dirname(__FILE__), '..', 'public')],
|
|
11
|
+
'layout_directories' => [],
|
|
12
|
+
'extensions' => []
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method_missing(method, *args)
|
|
17
|
+
method = method.to_s
|
|
18
|
+
if method[-1] == '='
|
|
19
|
+
@options[method[0...-1]] = args[0]
|
|
20
|
+
elsif method[-1] == '?'
|
|
21
|
+
@options.has_key?(method[0...-1])
|
|
22
|
+
else
|
|
23
|
+
@options[method]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def define(&block)
|
|
28
|
+
yield self
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Environment
|
|
2
|
+
def initialize
|
|
3
|
+
@env = ENV['YODEL_ENV'] || 'development'
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def method_missing(sym, *args)
|
|
7
|
+
sym = sym.to_s
|
|
8
|
+
if sym.end_with?('!')
|
|
9
|
+
@env = sym[0..-2]
|
|
10
|
+
elsif sym.end_with?('?')
|
|
11
|
+
@env == sym[0..-2]
|
|
12
|
+
else
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Yodel
|
|
2
|
+
MODELS_DIRECTORY_NAME = 'models'
|
|
3
|
+
PUBLIC_DIRECTORY_NAME = 'public'
|
|
4
|
+
LAYOUTS_DIRECTORY_NAME = 'layouts'
|
|
5
|
+
PARTIALS_DIRECTORY_NAME = 'partials'
|
|
6
|
+
MIGRATIONS_DIRECTORY_NAME = 'migrations'
|
|
7
|
+
ATTACHMENTS_DIRECTORY_NAME = 'attachments'
|
|
8
|
+
EXTENSION_LIB_DIRECTORY_NAME = 'lib'
|
|
9
|
+
YODEL_MIGRATIONS_DIRECTORY_NAME = 'yodel'
|
|
10
|
+
EXTENSION_MIGRATIONS_DIRECTORY_NAME = 'extensions'
|
|
11
|
+
SITE_MIGRATIONS_DIRECTORY_NAME = 'site'
|
|
12
|
+
SITE_YML_FILE_NAME = 'site.yml'
|
|
13
|
+
|
|
14
|
+
def self.config
|
|
15
|
+
@config ||= YodelConfig.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.env
|
|
19
|
+
@env ||= Environment.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class DomainNotFound < StandardError
|
|
2
|
+
attr_reader :domain
|
|
3
|
+
def initialize(domain, port)
|
|
4
|
+
@domain = domain
|
|
5
|
+
@port = (port == 80 ? nil : port)
|
|
6
|
+
super()
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def error
|
|
10
|
+
["The site '#{@domain.gsub('.yodel', '')}' has not been created yet"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
"<form action='http://yodel#{':' if @port}#{@port}/sites' method='post' class='inline'><input type='hidden' name='name'' value='#{@domain}'><a href='#' onclick='submit()'>Create a new site</a></form> or try a different address."
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class MissingRootDirectory < StandardError
|
|
2
|
+
def initialize(site, port)
|
|
3
|
+
@port = (port == 80 ? nil : port)
|
|
4
|
+
@site = site
|
|
5
|
+
super()
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def error
|
|
9
|
+
["The root directory for #{@site.name} is missing"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def description
|
|
13
|
+
"You can <a href='http://yodel#{':' if @port}#{@port}/sites?id=#{@site.id}'>select a new root directory</a>, or rename the existing directory back to '#{File.basename(@site.root_directory)}'."
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
require 'rack/mime'
|
|
3
|
+
require 'thread'
|
|
4
|
+
|
|
5
|
+
class StreamClosed < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class DevelopmentServer
|
|
9
|
+
SEPARATORS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
|
10
|
+
|
|
11
|
+
class Message
|
|
12
|
+
attr_accessor :status, :headers, :body, :env, :data, :message_type
|
|
13
|
+
MESSAGE_TYPES = {request: 0, response: 1, restart: 2, exit: 3}
|
|
14
|
+
|
|
15
|
+
def request?; @message_type == :request; end
|
|
16
|
+
def response?; @message_type == :response; end
|
|
17
|
+
def restart?; @message_type == :restart; end
|
|
18
|
+
def exit?; @message_type == :exit; end
|
|
19
|
+
|
|
20
|
+
def initialize(message_type)
|
|
21
|
+
@message_type = message_type
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.read(socket)
|
|
25
|
+
message = Message.new(nil)
|
|
26
|
+
message.message_type = MESSAGE_TYPES.keys[read_int(socket)]
|
|
27
|
+
|
|
28
|
+
if message.request? || message.response?
|
|
29
|
+
length = read_int(socket)
|
|
30
|
+
message.data = Marshal.load(socket.read(length))
|
|
31
|
+
|
|
32
|
+
if message.request?
|
|
33
|
+
message.data['rack.input'] = StringIO.new(message.data['rack.input'])
|
|
34
|
+
message.data['rack.errors'] = STDERR
|
|
35
|
+
elsif message.response?
|
|
36
|
+
message.data[2] = [message.data[2]]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
message
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def write(socket)
|
|
44
|
+
write_int(MESSAGE_TYPES[@message_type], socket)
|
|
45
|
+
payload = nil
|
|
46
|
+
|
|
47
|
+
if request?
|
|
48
|
+
# convert input (StringIO) to a normal string, and
|
|
49
|
+
# remove the reference to STDERR for transmission
|
|
50
|
+
@env['rack.input'] = @env['rack.input'].read if @env['rack.input'].is_a?(StringIO)
|
|
51
|
+
@env['rack.errors'] = nil
|
|
52
|
+
payload = Marshal.dump(@env)
|
|
53
|
+
elsif response?
|
|
54
|
+
body = ''
|
|
55
|
+
@body.each {|chunk| body << chunk.to_s}
|
|
56
|
+
payload = Marshal.dump([@status, @headers, body])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if payload
|
|
60
|
+
write_int(payload.length, socket)
|
|
61
|
+
socket.write(payload)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
def self.read_int(socket)
|
|
67
|
+
int = socket.read(4)
|
|
68
|
+
raise StreamClosed if int.nil?
|
|
69
|
+
int.unpack('L').first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def write_int(int, socket)
|
|
73
|
+
socket.write([int].pack('L'))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def initialize
|
|
78
|
+
spawn_server
|
|
79
|
+
@mutex = Mutex.new
|
|
80
|
+
@pid = nil
|
|
81
|
+
Signal.trap("SIGTERM") do
|
|
82
|
+
self.kill_child
|
|
83
|
+
Process.kill("SIGINT", Process.pid)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def kill_child
|
|
88
|
+
begin
|
|
89
|
+
Process.kill("SIGTERM", @pid) if @pid
|
|
90
|
+
@pid = nil
|
|
91
|
+
@client_socket.close
|
|
92
|
+
rescue
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def call(env)
|
|
97
|
+
@mutex.lock
|
|
98
|
+
parts = Rack::Utils.unescape(env["PATH_INFO"]).split(SEPARATORS)
|
|
99
|
+
return [403, {"Content-Type" => "text/plain"}, "Forbidden"] if parts.include? ".."
|
|
100
|
+
|
|
101
|
+
# pass the request through to a yodel server. if the server
|
|
102
|
+
# responds with a restart message (yodel source files have
|
|
103
|
+
# changed and need to be re-loaded) spawn a new server and
|
|
104
|
+
# write the request again (assume the request is successful)
|
|
105
|
+
request = Message.new(:request)
|
|
106
|
+
request.env = env
|
|
107
|
+
request.write(@client_socket)
|
|
108
|
+
response = Message.read(@client_socket)
|
|
109
|
+
|
|
110
|
+
if response.restart?
|
|
111
|
+
spawn_server
|
|
112
|
+
request.write(@client_socket)
|
|
113
|
+
response = Message.read(@client_socket)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# response data is a valid rack response
|
|
117
|
+
response.data
|
|
118
|
+
ensure
|
|
119
|
+
@mutex.unlock
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
protected
|
|
123
|
+
def spawn_server
|
|
124
|
+
# http requests are passed between the client (development server)
|
|
125
|
+
# and the server (yodel server) over a socket pair. Pipe's aren't
|
|
126
|
+
# used because the server may be forked for a long time and respond
|
|
127
|
+
# to multiple requests before being reaped
|
|
128
|
+
@client_socket.close unless @client_socket.nil?
|
|
129
|
+
@client_socket, @server_socket = UNIXSocket.pair
|
|
130
|
+
|
|
131
|
+
# child process loads yodel and responds to the request; if any
|
|
132
|
+
# source files have been modified since the server was started, send
|
|
133
|
+
# a restart message to the client and exit
|
|
134
|
+
@pid = Process.fork
|
|
135
|
+
if @pid.nil?
|
|
136
|
+
@client_socket.close
|
|
137
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'yodel')
|
|
138
|
+
closed = false
|
|
139
|
+
|
|
140
|
+
@application = Application.new
|
|
141
|
+
@modification_times = $LOADED_FEATURES.each_with_object({}) do |path, mtimes|
|
|
142
|
+
mtimes[path] = File.mtime(path) if File.exist?(path)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
loop do
|
|
146
|
+
begin
|
|
147
|
+
# block until a new request is received
|
|
148
|
+
request = Message.read(@server_socket)
|
|
149
|
+
|
|
150
|
+
# check for modified files
|
|
151
|
+
@modification_times.each do |path, modified_time|
|
|
152
|
+
if File.exist?(path) && File.mtime(path) > modified_time
|
|
153
|
+
message = Message.new(:restart)
|
|
154
|
+
message.write(@server_socket)
|
|
155
|
+
@server_socket.close
|
|
156
|
+
closed = true
|
|
157
|
+
break
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# kill the server if a file was changed
|
|
162
|
+
break if closed
|
|
163
|
+
|
|
164
|
+
# otherwise respond to the request
|
|
165
|
+
if request.request?
|
|
166
|
+
response = Message.new(:response)
|
|
167
|
+
response.status, response.headers, response.body = @application.call(request.data)
|
|
168
|
+
response.write(@server_socket)
|
|
169
|
+
end
|
|
170
|
+
rescue Interrupt, StreamClosed
|
|
171
|
+
exit
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
else
|
|
176
|
+
Process.detach(@pid)
|
|
177
|
+
@server_socket.close
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|