Compare commits

..

8 Commits

Author SHA1 Message Date
Min Zeya Phyo
98ac443182 Add empty shops.json in Dockerfile for cloud mode
The MyAesCrypt.export_to_file method expects config/shops.json to
exist as a cache of AES keys for tenant lookups. This file is in
.gitignore (runtime-generated) but needs to exist with at least an
empty JSON structure on first boot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:57:20 +08:00
Min Zeya Phyo
d0a607e976 Fix cloud mode: ERB secrets, Redis auth, and Redis URL
- secrets.rb: Process ERB tags in secrets.yml and use ||= to not
  overwrite existing ENV vars (was clobbering SERVER_MODE=cloud with
  literal ERB string, causing app to fall into 'application' mode
  and redirect to /en/activate)
- license.rb: Use ENV['REDIS_URL'] for all Redis.new calls instead
  of defaulting to localhost (infrastructure Redis requires auth)
- redis.yml: Use ERB to read REDIS_URL env var for production
- sidekiq.rb: Process ERB when loading redis.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:21:46 +08:00
Ubuntu
00369f96bd Regenerate Gemfile.lock (remove stale cups gem) 2026-02-06 06:48:29 +00:00
Ubuntu
8171710421 Relax Ruby version to ~> 2.6.0 (allows 2.6.10 in Docker) 2026-02-06 06:42:58 +00:00
Ubuntu
8a7a17f3ce Fix: install bundler 2.4.21 to match Gemfile.lock 2026-02-06 06:39:24 +00:00
Ubuntu
318443e918 Force add database.yml with ENV vars for Docker deployment 2026-02-06 06:34:57 +00:00
Ubuntu
942b8a54db Use ENV vars for database, secrets, and cable config (Docker support) 2026-02-06 06:34:50 +00:00
Claude
eb1010d8af Add Docker deployment files for Unity infrastructure migration 2026-02-06 06:28:52 +00:00
13 changed files with 103 additions and 61 deletions

View File

@@ -1,2 +1,7 @@
.git .git
.dockerignore log/*
tmp/*
vendor/bundle
node_modules
.bundle
.DS_Store

View File

@@ -1,20 +1,38 @@
FROM ruby:2.5 FROM ruby:2.6.10-slim-bullseye
RUN apt-get update -qq && apt-get install -y build-essential libmariadb-dev libcups2-dev libpq-dev nodejs tzdata
RUN mkdir /sxrestaurant # Install dependencies (MySQL client + ImageMagick for CarrierWave/MiniMagick)
RUN mkdir -p /sxrestaurant/tmp/puma RUN apt-get update -qq && apt-get install -y --no-install-recommends \
ENV RAILS_ENV production build-essential \
ENV RACK_ENV production default-libmysqlclient-dev \
WORKDIR /sxrestaurant nodejs \
#RUN gem install bundler git \
#COPY Gemfile /sxrestaurant/Gemfile curl \
#COPY Gemfile.lock /sxrestaurant/Gemfile.lock imagemagick \
#RUN bundle install --without development test libmagickwand-dev \
RUN echo "Asia/Rangoon" > /etc/timezone && rm -rf /var/lib/apt/lists/*
RUN dpkg-reconfigure -f noninteractive tzdata
RUN date WORKDIR /app
COPY . /sxrestaurant
RUN gem install bundler # Install correct bundler version (must match Gemfile.lock BUNDLED WITH)
#RUN bundle update --bundler RUN gem install bundler:2.4.21
RUN bundle install --without development test
RUN bundle exec rake assets:precompile # Install gems
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"] COPY Gemfile Gemfile.lock ./
RUN bundle install --deployment --without development test --jobs 4
# Copy application
COPY . .
# Create required directories and runtime files
RUN mkdir -p tmp/pids tmp/puma tmp/cache tmp/sockets log storage public/uploads \
&& echo '{"data":[]}' > config/shops.json
# Precompile assets
RUN RAILS_ENV=production SECRET_KEY_BASE=placeholder bundle exec rake assets:precompile 2>/dev/null || true
EXPOSE 3000
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
CMD ["/app/entrypoint.sh"]

View File

@@ -1,6 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '2.6.5' ruby '~> 2.6.0'
#ruby '2.5.7' #ruby '2.5.7'
@@ -52,7 +52,7 @@ gem 'mini_magick'
gem 'jquery-fileupload-rails', '~> 0.4.7' gem 'jquery-fileupload-rails', '~> 0.4.7'
#Report and Printing gems #Report and Printing gems
gem 'cups', '~> 0.0.7' # gem 'cups', '~> 0.0.7'
gem 'prawn' gem 'prawn'
gem 'prawn-table' gem 'prawn-table'

View File

@@ -96,7 +96,6 @@ GEM
concurrent-ruby (1.2.2) concurrent-ruby (1.2.2)
connection_pool (2.2.3) connection_pool (2.2.3)
crass (1.0.6) crass (1.0.6)
cups (0.0.7)
database_cleaner (1.8.5) database_cleaner (1.8.5)
diff-lcs (1.4.4) diff-lcs (1.4.4)
erubi (1.9.0) erubi (1.9.0)
@@ -360,7 +359,6 @@ DEPENDENCIES
carrierwave (~> 1.0) carrierwave (~> 1.0)
chartkick chartkick
coffee-rails (~> 4.2) coffee-rails (~> 4.2)
cups (~> 0.0.7)
database_cleaner database_cleaner
factory_girl_rails (~> 4.0) factory_girl_rails (~> 4.0)
faker faker

View File

@@ -37,7 +37,7 @@ class License
cache_license = nil cache_license = nil
##Get redis connection from connection pool ##Get redis connection from connection pool
redis = Redis.new redis = Redis.new(url: ENV['REDIS_URL'])
cache_license = redis.get(cache_key) cache_license = redis.get(cache_key)
Rails.logger.info "Cache key - " + cache_key.to_s Rails.logger.info "Cache key - " + cache_key.to_s
@@ -54,7 +54,7 @@ class License
#Rails.logger.info "License - " + response.parsed_response.to_s #Rails.logger.info "License - " + response.parsed_response.to_s
redis = Redis.new redis = Redis.new(url: ENV['REDIS_URL'])
redis.set(cache_key, Marshal.dump(@license)) redis.set(cache_key, Marshal.dump(@license))
# redis.sadd("License:cache:keys", cache_key) # redis.sadd("License:cache:keys", cache_key)
# Redis.current do |conn| # Redis.current do |conn|
@@ -110,7 +110,7 @@ class License
# if cache_license.nil? # if cache_license.nil?
cache = {"shop" => @activate["shop_name"], "key" => aes_key, "iv" => @activate["iv_key"], "renewable_date" => @activate["renewable_date"] } cache = {"shop" => @activate["shop_name"], "key" => aes_key, "iv" => @activate["iv_key"], "renewable_date" => @activate["renewable_date"] }
redis = Redis.new redis = Redis.new(url: ENV['REDIS_URL'])
redis.set(cache_key, Marshal.dump(cache)) redis.set(cache_key, Marshal.dump(cache))
# end # end
@@ -308,14 +308,14 @@ class License
cache_license = nil cache_license = nil
##Get redis connection from connection pool ##Get redis connection from connection pool
redis = Redis.new redis = Redis.new(url: ENV['REDIS_URL'])
cache_license = redis.get(cache_key) cache_license = redis.get(cache_key)
Rails.logger.info "Cache key - " + cache_key.to_s Rails.logger.info "Cache key - " + cache_key.to_s
if cache_license.nil? if cache_license.nil?
cache = {"shop" => shop_name, "key" => @data["secret_key"], "iv" => @data["iv_key"], "renewable_date" => @data["renewable_date"] } cache = {"shop" => shop_name, "key" => @data["secret_key"], "iv" => @data["iv_key"], "renewable_date" => @data["renewable_date"] }
redis = Redis.new redis = Redis.new(url: ENV['REDIS_URL'])
redis.set(cache_key, Marshal.dump(cache)) redis.set(cache_key, Marshal.dump(cache))
end end
return true return true
@@ -332,7 +332,7 @@ class License
cache_key = "shop:#{shop.chomp}" cache_key = "shop:#{shop.chomp}"
##Get redis connection from connection pool ##Get redis connection from connection pool
redis = Redis.new redis = Redis.new(url: ENV['REDIS_URL'])
cache_shop = redis.get(cache_key) cache_shop = redis.get(cache_key)
puts Marshal.load(cache_shop) puts Marshal.load(cache_shop)

View File

@@ -1,6 +1,6 @@
redis: &redis redis: &redis
adapter: redis adapter: redis
url: redis://localhost:6379/1 url: <%= ENV.fetch('REDIS_URL', 'redis://localhost:6379/1') %>
production: *redis production: *redis
development: *redis development: *redis

10
config/database.yml Normal file
View File

@@ -0,0 +1,10 @@
production:
adapter: mysql2
encoding: utf8mb4
pool: 25
host: <%= ENV.fetch('DATABASE_HOST', '54.151.188.42') %>
port: <%= ENV.fetch('DATABASE_PORT', '13306') %>
wait_timeout: 60
database: <%= ENV.fetch('DATABASE_NAME', 'foodcourt') %>
username: <%= ENV.fetch('DATABASE_USER', 'foodcourt') %>
password: <%= ENV.fetch('DATABASE_PASSWORD', 'foodcourt') %>

View File

@@ -1,6 +1,6 @@
config = YAML.load_file("#{Rails.root}/config/secrets.yml") config = YAML.load(ERB.new(File.read("#{Rails.root}/config/secrets.yml")).result)
config.fetch(Rails.env, {}).each do |key, value| config.fetch(Rails.env, {}).each do |key, value|
ENV[key.upcase] = value.to_s ENV[key.upcase] ||= value.to_s
end end
# SECRETS_CONFIG = YAML.load_file("#{Rails.root}/config/secrets.yml")[Rails.env] # SECRETS_CONFIG = YAML.load_file("#{Rails.root}/config/secrets.yml")[Rails.env]

View File

@@ -1,4 +1,4 @@
redis = YAML::load(File.open("#{ Rails.root }/config/redis.yml"))[::Rails.env] redis = YAML::load(ERB.new(File.read("#{ Rails.root }/config/redis.yml")).result)[::Rails.env]
Sidekiq.configure_server do |config| Sidekiq.configure_server do |config|
config.redis = { url: "#{ redis['url'] }/#{ redis['db'] }" } config.redis = { url: "#{ redis['url'] }/#{ redis['db'] }" }

17
config/puma_docker.rb Normal file
View File

@@ -0,0 +1,17 @@
# Puma configuration for Docker deployment
application_path = File.expand_path('..', __dir__)
directory application_path
environment ENV.fetch('RAILS_ENV') { 'production' }
pidfile "#{application_path}/tmp/puma/pid"
state_path "#{application_path}/tmp/puma/state"
# Log to stdout/stderr in Docker
stdout_redirect '/dev/stdout', '/dev/stderr', true
# Use PORT env var (default 3000 for Coolify)
port ENV.fetch('PORT') { 3000 }
workers ENV.fetch('WEB_CONCURRENCY') { 3 }
preload_app!
threads 5, 16

View File

@@ -10,4 +10,4 @@ test:
production: production:
<<: *default <<: *default
url: redis://127.0.0.1:6379 url: <%= ENV.fetch('REDIS_URL', 'redis://127.0.0.1:6379') %>

View File

@@ -1,30 +1,16 @@
# Be sure to restart your server when you modify this file.
# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rails secret` to generate a secure secret key.
# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
development: development:
secret_key_base: b61d85f8ed2a1a9e0eeece3443b3e8f838d002cc1d9f32115d8e93db920e2957adfedc57501d44741211538f3108b742cdeada87d5bfae796c53da1f90a3cd61 secret_key_base: b61d85f8ed2a1a9e0eeece3443b3e8f838d002cc1d9f32115d8e93db920e2957adfedc57501d44741211538f3108b742cdeada87d5bfae796c53da1f90a3cd61
sx_provision_url: connect.smartsales.asia/api #connect.smartsales.dev/api #connect.smartsales.asia/api #provision.zsai.ws/api sx_provision_url: connect.smartsales.asia/api
server_mode: application server_mode: application
cipher_type: AES-256-CBC cipher_type: AES-256-CBC
sx_key: Wh@t1$C2L sx_key: Wh@t1$C2L
test: test:
secret_key_base: 5c92143fd4a844fdaf8b22aba0cda22ef1fc68f1b26dd3d40656866893718ae5e58625b4c3a5dc86b04c8be0a505ec0ebc0be3bf52249a3d1e0c1334ee591cf0 secret_key_base: 5c92143fd4a844fdaf8b22aba0cda22ef1fc68f1b26dd3d40656866893718ae5e58625b4c3a5dc86b04c8be0a505ec0ebc0be3bf52249a3d1e0c1334ee591cf0
# Do not keep production secrets in the repository,
# instead read values from the environment.
production: production:
secret_key_base: c4bc81065013f9a3506d385bcbd49586c42e586488144b0de90c7da36867de9fa880f46b5c4f86f0ce9b7c783bb5a73bdb0e5605a47716567294390e726d3e22 secret_key_base: <%= ENV.fetch('SECRET_KEY_BASE', 'c4bc81065013f9a3506d385bcbd49586c42e586488144b0de90c7da36867de9fa880f46b5c4f86f0ce9b7c783bb5a73bdb0e5605a47716567294390e726d3e22') %>
sx_provision_url: connect.smartsales.asia/api #l.doemal.app/api #52.221.188.144:9292/api #192.168.1.147:3002/api sx_provision_url: <%= ENV.fetch('SX_PROVISION_URL', 'connect.smartsales.asia/api') %>
server_mode: application server_mode: <%= ENV.fetch('SERVER_MODE', 'cloud') %>
cipher_type: AES-256-CBC cipher_type: <%= ENV.fetch('CIPHER_TYPE', 'AES-256-CBC') %>
sx_key: Wh@t1$C2L sx_key: <%= ENV.fetch('SX_KEY', 'Wh@t1$C2L') %>

8
entrypoint.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -e
# Start Sidekiq in background
bundle exec sidekiq -C config/sidekiq.yml -e production &
# Start Puma on port 3000
exec bundle exec puma -C config/puma_docker.rb