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