forked from Lyrinox-Technologies/Source-License
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsl_configure
More file actions
407 lines (343 loc) · 11.2 KB
/
Copy pathsl_configure
File metadata and controls
407 lines (343 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'securerandom'
require 'bcrypt'
require 'io/console'
require 'fileutils'
require 'optparse'
class SourceLicenseConfigurator
ENV_FILE = '.env'
BACKUP_SUFFIX = '.backup'
# Define which environment variables should be auto-generated with secure values
SECURE_VARIABLES = {
'APP_SECRET' => :secret_key,
'JWT_SECRET' => :jwt_secret,
'LICENSE_HASH_SALT' => :license_salt,
'LICENSE_JWT_SECRET' => :license_jwt_secret,
'INITIAL_ADMIN_PASSWORD' => :admin_password,
'STRIPE_WEBHOOK_SECRET' => :webhook_secret,
}.freeze
# Variables that should be prompted for but not auto-generated
IMPORTANT_VARIABLES = %w[
APP_NAME APP_ENV APP_HOST PORT
SUPPORT_EMAIL ORGANIZATION_NAME ORGANIZATION_URL
DATABASE_ADAPTER DATABASE_NAME DATABASE_HOST DATABASE_PORT
DATABASE_USER DATABASE_PASSWORD
INITIAL_ADMIN_EMAIL
STRIPE_PUBLISHABLE_KEY STRIPE_SECRET_KEY
PAYPAL_CLIENT_ID PAYPAL_CLIENT_SECRET PAYPAL_ENVIRONMENT
SMTP_HOST SMTP_PORT SMTP_USERNAME SMTP_PASSWORD
].freeze
def initialize
@env_vars = {}
@comments = {}
@original_order = []
end
def run
puts '🔧 Source-License Configuration Tool'
puts '=' * 50
puts
unless File.exist?(ENV_FILE)
puts '❌ .env file not found. Please copy .env.example to .env first.'
exit 1
end
load_env_file
puts 'This tool will help you configure your Source-License installation.'
puts 'For security variables, we can auto-generate secure values for you.'
puts
configure_variables
create_backup
write_env_file
puts
puts '✅ Configuration complete!'
puts "📋 Your settings have been saved to #{ENV_FILE}"
puts "💾 A backup was created at #{ENV_FILE}#{BACKUP_SUFFIX}"
puts
puts '🚀 You can now start your Source-License application!'
end
private
def load_env_file
current_comment_block = []
File.readlines(ENV_FILE, chomp: true).each do |line|
if line.strip.empty?
current_comment_block << line
elsif line.start_with?('#')
current_comment_block << line
elsif line.include?('=')
# This is a variable line
key, value = line.split('=', 2)
key = key.strip
@env_vars[key] = value || ''
@original_order << key
# Associate any accumulated comments with this variable
if current_comment_block.any?
@comments[key] = current_comment_block.join("\n")
current_comment_block = []
end
end
end
end
def configure_variables
puts '📝 Configuring environment variables...'
puts
# First, handle secure variables
puts '🔐 Security Variables'
puts '-' * 20
SECURE_VARIABLES.each do |var_name, generator_type|
next unless @env_vars.key?(var_name)
configure_secure_variable(var_name, generator_type)
end
puts
puts '⚙️ Application Variables'
puts '-' * 25
IMPORTANT_VARIABLES.each do |var_name|
next unless @env_vars.key?(var_name)
configure_regular_variable(var_name)
end
# Handle any remaining variables
remaining_vars = @original_order - SECURE_VARIABLES.keys - IMPORTANT_VARIABLES
return unless remaining_vars.any?
puts
puts '📋 Other Variables'
puts '-' * 15
remaining_vars.each do |var_name|
configure_regular_variable(var_name)
end
end
def configure_secure_variable(var_name, generator_type)
current_value = @env_vars[var_name]
puts "🔑 #{var_name}"
puts " Current: #{mask_value(current_value)}"
if needs_secure_generation?(current_value)
print ' This appears to be a default/insecure value. Generate secure value? (Y/n): '
$stdout.flush
response = $stdin.gets.chomp.downcase
if response.empty? || response == 'y' || response == 'yes'
generate_and_set_secure_value(var_name, generator_type)
return
elsif prompt_keep_current_value
# User declined auto-generation, ask if they want to keep current or enter manually
return
end
elsif prompt_keep_current_value
return
end
# Manual entry (only reached if user wants to change the value)
handle_manual_entry_or_generation(var_name, generator_type)
end
def configure_regular_variable(var_name)
current_value = @env_vars[var_name]
puts "📋 #{var_name}"
puts " Current: #{current_value}"
print ' Keep current value? (Y/n): '
$stdout.flush # Ensure the prompt is displayed immediately
response = $stdin.gets.chomp.downcase
# Only treat explicit 'y' or 'yes' as acceptance, require actual input
if %w[y yes].include?(response)
puts ' ✅ Keeping current value'
elsif %w[n no].include?(response)
print ' Enter new value: '
$stdout.flush
new_value = $stdin.gets.chomp
@env_vars[var_name] = new_value
puts ' ✅ Updated'
elsif response.empty?
# Default to keeping current value when just pressing Enter
puts ' ✅ Keeping current value'
else
# Invalid response, default to keeping current value
puts ' ❓ Invalid response, keeping current value'
end
end
def needs_secure_generation?(value)
insecure_patterns = [
/your_.*_here/i,
/change_this/i,
/change_in_production/i,
/dev_.*_salt/i,
/dev_.*_secret/i,
/minimum_required/i,
/admin1234/i,
]
insecure_patterns.any? { |pattern| value.match?(pattern) }
end
def generate_secure_value(type)
case type
when :secret_key
SecureRandom.hex(32) # 64 character hex string
when :jwt_secret
SecureRandom.base64(48) # Base64 encoded 48 bytes
when :license_salt
"#{SecureRandom.hex(32)}_salt_#{Time.now.to_i}"
when :license_jwt_secret
"license_#{SecureRandom.hex(32)}"
when :admin_password
# Generate a secure 16-character password with mixed case, numbers, and symbols
charset = [('a'..'z'), ('A'..'Z'), (0..9), ['!', '@', '#', '$', '%', '^', '&', '*']].map(&:to_a).flatten
Array.new(16) { charset.sample }.join
when :webhook_secret
"whsec_#{SecureRandom.hex(32)}"
else
SecureRandom.hex(32)
end
end
def mask_value(value)
return '(empty)' if value.nil? || value.empty?
return value if value.length <= 8
# Show first 4 and last 4 characters, mask the middle
"#{value[0..3]}#{'*' * [value.length - 8, 4].max}#{value[-4..]}"
end
# Helper methods to eliminate duplicate branch bodies
def generate_and_set_secure_value(var_name, generator_type)
new_value = generate_secure_value(generator_type)
@env_vars[var_name] = new_value
puts ' ✅ Generated new secure value'
end
def prompt_keep_current_value
print ' Keep current value? (Y/n): '
$stdout.flush
keep_response = $stdin.gets.chomp.downcase
if keep_response.empty? || keep_response == 'y' || keep_response == 'yes'
puts ' ✅ Keeping current value'
true
else
false
end
end
def handle_manual_entry_or_generation(var_name, generator_type)
print ' Enter new value (or press Enter to generate): '
$stdout.flush
input = $stdin.gets.chomp
if input.empty?
generate_and_set_secure_value(var_name, generator_type)
else
@env_vars[var_name] = input
puts ' ✅ Updated'
end
end
def create_backup
backup_file = "#{ENV_FILE}#{BACKUP_SUFFIX}"
FileUtils.cp(ENV_FILE, backup_file)
puts "💾 Created backup: #{backup_file}"
end
def write_env_file
File.open(ENV_FILE, 'w') do |file|
@original_order.each do |key|
# Write any comments associated with this variable
file.puts @comments[key] if @comments[key]
# Write the variable
file.puts "#{key}=#{@env_vars[key]}"
end
end
puts "✅ Updated #{ENV_FILE}"
end
end
def show_help
puts '🔧 Source-License Configuration Tool'
puts '=' * 50
puts
puts 'USAGE:'
puts ' ruby ./sl_configure [options]'
puts
puts 'OPTIONS:'
puts ' -h, --help Show this help message'
puts ' -v, --version Show version information'
puts ' -r, --reset Reset .env to default values from .env.example'
puts ' --restore Restore .env from backup (.env.backup)'
puts
puts 'DESCRIPTION:'
puts ' Interactive tool to configure your Source-License .env file.'
puts ' Automatically detects insecure default values and can generate'
puts ' cryptographically secure replacements for sensitive variables.'
puts
puts 'FEATURES:'
puts ' 🔐 Automatic security generation for secrets, salts, and passwords'
puts ' 📝 Interactive configuration for all environment variables'
puts ' 💾 Automatic backup creation before making changes'
puts ' 🔍 Smart detection of variables needing secure generation'
puts ' 🔄 Reset to defaults and restore from backup functionality'
puts
puts 'EXAMPLES:'
puts ' ruby ./sl_configure # Start interactive configuration'
puts ' ruby ./sl_configure --help # Show this help'
puts ' ruby ./sl_configure --reset # Reset .env to defaults'
puts ' ruby ./sl_configure --restore # Restore from backup'
puts
end
def show_version
puts 'Source-License Configuration Tool v1.0.0'
puts 'Part of the Source-License platform'
end
def reset_to_default
unless File.exist?('.env.example')
puts '❌ .env.example file not found. Cannot reset to defaults.'
exit 1
end
if File.exist?('.env')
backup_file = ".env.backup.#{Time.now.to_i}"
FileUtils.cp('.env', backup_file)
puts "💾 Created backup of current .env: #{backup_file}"
end
FileUtils.cp('.env.example', '.env')
puts '✅ Successfully reset .env to default values from .env.example'
puts '🔧 Run "ruby ./sl_configure" to configure your settings'
end
def restore_backup
backup_file = '.env.backup'
unless File.exist?(backup_file)
puts '❌ No backup file found (.env.backup)'
puts 'ℹ️ Backups are created when you run the configuration tool'
exit 1
end
if File.exist?('.env')
current_backup = ".env.current.#{Time.now.to_i}"
FileUtils.cp('.env', current_backup)
puts "💾 Created backup of current .env: #{current_backup}"
end
FileUtils.cp(backup_file, '.env')
puts '✅ Successfully restored .env from backup'
end
# Parse command line arguments
if __FILE__ == $0
options = {}
OptionParser.new do |opts|
opts.banner = 'Usage: ruby ./sl_configure [options]'
opts.on('-h', '--help', 'Show help message') do
show_help
exit 0
end
opts.on('-v', '--version', 'Show version') do
show_version
exit 0
end
opts.on('-r', '--reset', 'Reset .env to default values from .env.example') do
options[:reset] = true
end
opts.on('--restore', 'Restore .env from backup (.env.backup)') do
options[:restore] = true
end
end.parse!
# Handle special commands
if options[:reset]
reset_to_default
exit 0
end
if options[:restore]
restore_backup
exit 0
end
# Run the main configurator
begin
configurator = SourceLicenseConfigurator.new
configurator.run
rescue Interrupt
puts
puts '❌ Configuration cancelled by user'
exit 1
rescue StandardError => e
puts
puts "❌ Error: #{e.message}"
puts 'Please check your .env file and try again.'
exit 1
end
end