Create testbed to test beamer scenarios in Fizzy

This commit is contained in:
Stanko K.R.
2025-10-13 13:46:00 +02:00
parent 0e0103c492
commit d3816bc212
8 changed files with 333 additions and 7 deletions
+1
View File
@@ -61,6 +61,7 @@ RUN apt-get update -qq && \
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails
COPY --from=beamer /home/beamer/bin/beamer.so /rails/bin/lib/beamer.so
# COPY --from=beamer /home/beamer/bin/beamer /rails/bin/beamer
# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
+46
View File
@@ -0,0 +1,46 @@
# syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.7
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here
WORKDIR /rails
# Set development environment
ENV RAILS_ENV="development" \
BUNDLE_PATH="/usr/local/bundle"
# Install packages needed for development
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libsqlite3-0 libvips build-essential pkg-config git ffmpeg groff libreoffice-writer libreoffice-impress libreoffice-calc mupdf-tools libyaml-dev libssl-dev && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application gems
COPY Gemfile Gemfile.lock .ruby-version ./
COPY lib/bootstrap.rb ./lib/bootstrap.rb
COPY gems ./gems/
RUN --mount=type=secret,id=GITHUB_TOKEN --mount=type=cache,id=fizzy-devbundle-${RUBY_VERSION},sharing=locked,target=/devbundle \
gem install bundler && \
BUNDLE_PATH=/devbundle BUNDLE_GITHUB__COM="$(cat /run/secrets/GITHUB_TOKEN):x-oauth-basic" bundle install && \
cp -a /devbundle/. "$BUNDLE_PATH"/ && \
bundle clean --force
# Fetch beamer library
FROM registry.37signals.com/basecamp/beamer:vfs AS beamer
# Final stage
FROM base
# Copy beamer
COPY --from=beamer /home/beamer/bin/beamer.so /usr/local/lib/beamer/beamer.so
COPY --from=beamer /usr/local/bin/beamer /usr/local/bin/beamer
# Entrypoint prepares the database
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default
EXPOSE 3000
EXPOSE 3006
EXPOSE 5001
CMD ["./bin/thrust", "./bin/rails", "server"]
+3
View File
@@ -0,0 +1,3 @@
web: bin/thrust bin/rails server
beamer: beamer --debug --directory ./storage/ run --primary ${BEAMER_PRIMARY:-$(hostname)}
migrations: ./bin/beamer-testbed run-migrations --debug --directory ./storage/
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env bash
set -e
COMPOSE_OPTIONS="-f docker-compose.beamer-testbed.yml"
# Show usage
show_usage() {
echo "Usage: $0 {start|stop|logs|console|bash|server|run-migrations} [options]"
echo ""
echo "Commands:"
echo " start Build and start all services"
echo " stop Stop all services"
echo " logs Show logs (pass -f to follow)"
echo " console [node] Open Rails console (default: fizzy-01)"
echo " bash [node] Open bash shell (default: fizzy-01)"
echo " server Start web server and beamer using foreman"
echo " run-migrations Wait for beamer, then run migrations if primary"
exit 1
}
# Get all fizzy-* services from docker compose
get_fizzy_services() {
fizzy_services=$(docker compose $COMPOSE_OPTIONS config --services | grep '^fizzy-')
if [ -z "$fizzy_services" ]; then
echo "Error: No fizzy-* services found in docker-compose file"
exit 1
fi
echo "$fizzy_services"
}
# Function to wait for a container to be healthy
wait_for_container() {
local container=$1
local max_attempts=60
local attempt=0
echo "Waiting for $container to be ready..."
while [ $attempt -lt $max_attempts ]; do
if docker compose $COMPOSE_OPTIONS exec -T "$container" curl -f -s http://localhost:3000/up >/dev/null 2>&1; then
echo "$container is ready!"
return 0
fi
attempt=$((attempt + 1))
echo "$container isn't yet ready on attempt $attempt/$max_attempts"
sleep 2
done
echo "Error: $container failed to become ready after $max_attempts attempts"
return 1
}
# Function to register a container with kamal-proxy
register_with_proxy() {
local container=$1
local target_name="fizzy-${container##*-}"
echo "Registering $container with kamal-proxy as $target_name..."
docker compose $COMPOSE_OPTIONS exec -T kamal-proxy \
kamal-proxy deploy fizzy \
--target "$container:3000" \
--host "fizzy.localhost"
echo "$container registered successfully!"
}
# Start command
cmd_start() {
local fizzy_services=$(get_fizzy_services)
local first_fizzy=$(echo "$fizzy_services" | head -n 1)
# Get GitHub token from gh CLI
echo "Getting GitHub token from gh CLI..."
export GITHUB_TOKEN=$(gh auth token)
if [ -z "$GITHUB_TOKEN" ]; then
echo "Error: Failed to get GitHub token from gh CLI"
echo "Make sure you're authenticated with: gh auth login"
exit 1
fi
# Create tmp directories with proper permissions
echo "Creating temporary directories..."
for service in $fizzy_services; do
mkdir -p "./tmp/beamer-testbed/$service/storage"
mkdir -p "./tmp/beamer-testbed/$service/tmp"
done
echo "Building Docker image for $first_fizzy..."
docker compose $COMPOSE_OPTIONS build "$first_fizzy"
echo "Starting services in detached mode..."
docker compose $COMPOSE_OPTIONS down
docker compose $COMPOSE_OPTIONS up -d
# Wait for and register each fizzy container
for container in $fizzy_services; do
wait_for_container "$container"
register_with_proxy "$container"
done
echo ""
echo "All containers are ready and registered!"
echo "You can access the application at: http://fizzy.localhost"
}
# Stop command
cmd_stop() {
echo "Stopping services..."
docker compose $COMPOSE_OPTIONS down
echo "Services stopped."
}
# Logs command
cmd_logs() {
docker compose $COMPOSE_OPTIONS logs "$@"
}
# Console command
cmd_console() {
local node="${1:-fizzy-01}"
echo "Opening Rails console in $node..."
docker compose $COMPOSE_OPTIONS exec "$node" bin/rails console
}
# Bash command
cmd_bash() {
local node="${1:-fizzy-01}"
echo "Opening bash shell in $node..."
docker compose $COMPOSE_OPTIONS exec "$node" bash
}
# Server command (runs inside container)
cmd_server() {
# Check if foreman is installed
if ! command -v foreman >/dev/null 2>&1; then
echo "Foreman not found, installing..."
gem install foreman
fi
# Clean up any stale .beamer.sock files
echo "Cleaning up stale socket files..."
find . -path '*/.beamer/*/.beamer.sock' -type s -delete 2>/dev/null || true
echo "Starting web server and beamer with foreman..."
exec foreman start -f Procfile.beamer-testbed
}
# Run migrations command (runs inside container)
cmd_run_migrations() {
# Store all arguments as BEAMER_OPTIONS
BEAMER_OPTIONS="$*"
echo "Waiting for beamer to be ready..."
# Wait for beamer status to succeed
while ! beamer $BEAMER_OPTIONS status >/dev/null 2>&1; do
echo "Beamer not ready yet, waiting..."
sleep 1
done
echo "Beamer is ready!"
sleep 1
# Check if this is the primary node
if beamer $BEAMER_OPTIONS is-primary; then
echo "This is the primary node, running migrations..."
./bin/rails db:prepare
echo "Migrations complete!"
else
echo "This is not the primary node, skipping migrations."
fi
# Wait indefinitely until interrupted
echo "Waiting indefinitely (press Ctrl-C to exit)..."
while true; do
sleep 3600
done
}
# Main command dispatch
case "${1:-}" in
start)
cmd_start
;;
stop)
cmd_stop
;;
logs)
shift
cmd_logs "$@"
;;
console)
shift
cmd_console "$@"
;;
bash)
shift
cmd_bash "$@"
;;
server)
cmd_server
;;
run-migrations)
shift
cmd_run_migrations "$@"
;;
*)
show_usage
;;
esac
+3 -1
View File
@@ -14,7 +14,9 @@ default: &default
development:
primary:
<<: *default
database: storage/tenants/<%= Rails.env %>/%{tenant}/db/main.sqlite3
<% database_path = "storage/tenants/#{Rails.env}/%{tenant}/db/main.sqlite3" %>
<% database_path = "file:#{database_path}?vfs=beamer" if %w[1 true].include?(ENV["BEAMER"]&.downcase) %>
database: <%= database_path %>
tenanted: true
untenanted:
<<: *default
+1 -1
View File
@@ -76,7 +76,7 @@ Rails.application.configure do
config.action_mailer.raise_delivery_errors = false
end
config.hosts = %w[ fizzy.localhost localhost 127.0.0.1 ]
config.hosts = %w[ fizzy.localhost localhost 127.0.0.1 ] + [ /^fizzy-\d+(:\d+)$/ ]
# Set host to be used by links generated in mailer and notification view templates.
config.action_controller.default_url_options = { host: config.hosts.first, port: 3006 }
+11 -5
View File
@@ -9,11 +9,17 @@
# file will be removed.
Rails.application.config.after_initialize do
beamer_extension_path = Rails.root.join("bin/lib/beamer.so")
paths = [
Rails.root.join("bin/lib/beamer.so"),
Pathname("/usr/local/lib/beamer/beamer.so")
]
if beamer_extension_path.exist?
db = SQLite3::Database.new ":memory:"
db.enable_load_extension(true)
db.load_extension(beamer_extension_path)
paths.each do |beamer_extension_path|
if beamer_extension_path.exist?
db = SQLite3::Database.new ":memory:"
db.enable_load_extension(true)
db.load_extension(beamer_extension_path)
break
end
end
end
+51
View File
@@ -0,0 +1,51 @@
x-app: &app
image: "basecamp/fizzy:dev"
build:
context: .
dockerfile: Dockerfile.dev
secrets:
- GITHUB_TOKEN
volumes:
- .:/rails
environment:
- RAILS_ENV=development
- BINDING=0.0.0.0
- BEAMER=true
- BEAMER_PRIMARY=fizzy-01
command: [ "./bin/beamer-testbed", "server" ]
services:
kamal-proxy:
image: basecamp/kamal-proxy:next
ports:
- 127.0.0.1:80:80
- 127.0.0.1:443:443
command: [ "kamal-proxy", "run", "--debug" ]
fizzy-01:
<<: *app
hostname: fizzy-01
volumes:
- .:/rails
- ./tmp/beamer-testbed/fizzy-01/storage:/rails/storage
- ./tmp/beamer-testbed/fizzy-01/:/rails/tmp
fizzy-02:
<<: *app
hostname: fizzy-02
volumes:
- .:/rails
- ./tmp/beamer-testbed/fizzy-02/storage:/rails/storage
- ./tmp/beamer-testbed/fizzy-02/:/rails/tmp
fizzy-03:
<<: *app
hostname: fizzy-03
volumes:
- .:/rails
- ./tmp/beamer-testbed/fizzy-03/storage:/rails/storage
- ./tmp/beamer-testbed/fizzy-03/:/rails/tmp
secrets:
GITHUB_TOKEN:
environment: GITHUB_TOKEN