Skip to content

Commit

Permalink
Lightweight local environment (laptop friendly) (#171)
Browse files Browse the repository at this point in the history
* ops(compose): tom, synpase, lemonLDAP::NG, sqlite

	This compose is tailored to deploy the lightest environment
possible. It only deploy mandatory services and uses SQLite as db
engine.

	TODO:
	  - [ ] tchat web flutter frontend

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* ops(compose): twake chat frontend

	Twake Chat flutter web is now deployed as a container. The
latest available image is used.

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* chore(lemon): add more default users

	added:
	  - dwho / dwho
	  - rtyler / rtyler
	  - jbinks / jbinks

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* doc(readme): update docker informations

	Adds more details about the available configuration variables
and how to fire up a working local environment.

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* style(tom/invitation): just lint fix

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* fix(sql): missing IF NOT EXISTS statements

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* ops(ssl/chat): better ssl redirect with chat

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* doc(docker): a word about rootCA

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* chore(compose): cleanup configs

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* ops(docker): arg TOM_EXTRA_DEPS

	Using TOM_EXTRA_DEPS one is allowed to install a possibly
missing dependency during docker build.

	e.g. for compose we need sqlite3

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

* ops(docker): no more issue with sqlite3

	This commit removes the use of TOM_EXTRA_DEPS and uses npm
explore to finalize the installation of sqlite3, producing a lighter
docker image ~350MB.

Signed-off-by: Pierre 'McFly' Marty <[email protected]>

---------

Signed-off-by: Pierre 'McFly' Marty <[email protected]>
  • Loading branch information
pm-McFly authored Jan 30, 2025
1 parent 8fa4fef commit 427dba0
Show file tree
Hide file tree
Showing 48 changed files with 2,405 additions and 197 deletions.
17 changes: 17 additions & 0 deletions .compose/chat/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"application_name": "Twake Chat",
"application_welcome_message": "Welcome to Twake Chat!",
"default_homeserver": "docker.localhost",
"privacy_url": "https://twake.app/en/privacy/",
"render_html": true,
"hide_redacted_events": false,
"hide_unknown_events": false,
"issue_id": "",
"app_grid_dashboard_available": false,
"homeserver": "https://matrix.docker.localhost/",
"platform": "localDebug",
"default_max_upload_avatar_size_in_bytes": 1000000,
"dev_mode": true,
"enable_logs": true,
"support_url": "https://twake.app/support"
}
14 changes: 14 additions & 0 deletions .compose/chat/default.conf.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
server {
listen ${TWAKECHAT_LISTEN_PORT} default;
listen [::]:${TWAKECHAT_LISTEN_PORT} default;

location = / {
return 301 https://chat.docker.localhost${TWAKECHAT_BASE_HREF};
}

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
247 changes: 247 additions & 0 deletions .compose/haproxy/cors.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
--
-- Cross-origin Request Sharing (CORS) implementation for HAProxy Lua host
--
-- CORS RFC:
-- https://www.w3.org/TR/cors/
--
-- Copyright (c) 2019. Nick Ramirez <[email protected]>
-- Copyright (c) 2019. HAProxy Technologies, LLC.

local M={}

-- Loops through array to find the given string.
-- items: array of strings
-- test_str: string to search for
function contains(items, test_str)
for _,item in pairs(items) do
if item == test_str then
return true
end
end

return false
end

M.wildcard_origin_allowed = function(allowed_origins)
if contains(allowed_origins, "*") then
return "*"
end
return nil
end

M.specifies_scheme = function(s)
return string.find(s, "^%a+://") ~= nil
end

M.specifies_generic_scheme = function(s)
return string.find(s, "^//") ~= nil
end

M.begins_with_dot = function(s)
return string.find(s, "^%.") ~= nil
end

M.trim = function(s)
return s:gsub("%s+", "")
end

M.build_pattern = function(pattern)
-- remove spaces
pattern = M.trim(pattern)

if pattern ~= nil and pattern ~= '' then
-- if there is no scheme and the pattern does not begin with a dot,
-- add the generic scheme to the beginning of the pattern
if M.specifies_scheme(pattern) == false and M.specifies_generic_scheme(pattern) == false and M.begins_with_dot(pattern) == false then
pattern = "//" .. pattern
end

-- escape dots and dashes in pattern
pattern = pattern:gsub("([%.%-])", "%%%1")

-- an asterisk for the port means allow all ports
if string.find(pattern, "[:]+%*$") ~= nil then
pattern = pattern:gsub("[:]+%*$", "[:]+[0-9]+")
end

-- append end character
pattern = pattern .. "$"
return pattern
end

return nil
end

-- If the given origin is found within the allowed_origins string, it is returned. Otherwise, nil is returned.
-- origin: The value from the 'origin' request header
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,https://localhost:8080,//test.com)
-- e.g. localhost : allow http(s)://localhost
-- e.g. //localhost : allow http(s)://localhost
-- e.g. https://mydomain.com : allow only HTTPS of mydomain.com
-- e.g. http://mydomain.com : allow only HTTP of mydomain.com
-- e.g. http://mydomain.com:8080 : allow only HTTP of mydomain.com from port 8080
-- e.g. //mydomain.com : allow only http(s)://mydomain.com
-- e.g. .mydomain.com : allow ALL subdomains of mydomain.com from ALL source ports
-- e.g. .mydomain.com:443 : allow ALL subdomains of mydomain.com from default HTTPS source port
--
-- e.g. ".mydomain.com:443, //mydomain.com:443, //localhost"
-- allows all subdomains and main domain of mydomain.com only for HTTPS from default HTTPS port and allows
-- all HTTP and HTTPS connections from ALL source port for localhost
--
M.get_allowed_origin = function(origin, allowed_origins)
if origin ~= nil then
-- if wildcard (*) is allowed, return it, which allows all origins
wildcard_origin = M.wildcard_origin_allowed(allowed_origins)
if wildcard_origin ~= nil then
return wildcard_origin
end

for index, allowed_origin in ipairs(allowed_origins) do
pattern = M.build_pattern(allowed_origin)

if pattern ~= nil then
if origin:match(pattern) then
core.Debug("Test: " .. pattern .. ", Origin: " .. origin .. ", Match: yes")
return origin
else
core.Debug("Test: " .. pattern .. ", Origin: " .. origin .. ", Match: no")
end
end
end
end

return nil
end

-- Adds headers for CORS preflight request and then attaches them to the response
-- after it comes back from the server. This works with versions of HAProxy prior to 2.2.
-- The downside is that the OPTIONS request must be sent to the backend server first and can't
-- be intercepted and returned immediately.
-- txn: The current transaction object that gives access to response properties
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
-- allowed_headers: Comma-delimited list of allowed headers. (e.g. X-Header1,X-Header2)
function preflight_request_ver1(txn, allowed_methods, allowed_headers)
core.Debug("CORS: preflight request received")
txn.http:res_set_header("Access-Control-Allow-Methods", allowed_methods)
txn.http:res_set_header("Access-Control-Allow-Headers", allowed_headers)
txn.http:res_set_header("Access-Control-Max-Age", 600)
core.Debug("CORS: attaching allowed methods to response")
end

-- Add headers for CORS preflight request and then returns a 204 response.
-- The 'reply' function used here is available in HAProxy 2.2+. It allows HAProxy to return
-- a reply without contacting the server.
-- txn: The current transaction object that gives access to response properties
-- origin: The value from the 'origin' request header
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
-- allowed_headers: Comma-delimited list of allowed headers. (e.g. X-Header1,X-Header2)
function preflight_request_ver2(txn, origin, allowed_methods, allowed_origins, allowed_headers)
core.Debug("CORS: preflight request received")

local reply = txn:reply()
reply:set_status(204, "No Content")
reply:add_header("Content-Type", "text/html")
reply:add_header("Access-Control-Allow-Methods", allowed_methods)
reply:add_header("Access-Control-Allow-Headers", allowed_headers)
reply:add_header("Access-Control-Max-Age", 600)

local allowed_origin = M.get_allowed_origin(origin, allowed_origins)

if allowed_origin == nil then
core.Debug("CORS: " .. origin .. " not allowed")
else
core.Debug("CORS: " .. origin .. " allowed")
reply:add_header("Access-Control-Allow-Origin", allowed_origin)

if allowed_origin ~= "*" then
reply:add_header("Vary", "Accept-Encoding,Origin")
end
end

core.Debug("CORS: Returning reply to preflight request")
txn:done(reply)
end

-- When invoked during a request, captures the origin header if present and stores it in a private variable.
-- If the request is OPTIONS and it is a supported version of HAProxy, returns a preflight request reply.
-- Otherwise, the preflight request header is added to the response after it has returned from the server.
-- txn: The current transaction object that gives access to response properties
-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
-- allowed_headers: Comma-delimited list of allowed headers. (e.g. X-Header1,X-Header2)
function cors_request(txn, allowed_methods, allowed_origins, allowed_headers)
local headers = txn.http:req_get_headers()
local transaction_data = {}
local origin = nil
local allowed_origins = core.tokenize(allowed_origins, ",")

if headers["origin"] ~= nil and headers["origin"][0] ~= nil then
core.Debug("CORS: Got 'Origin' header: " .. headers["origin"][0])
origin = headers["origin"][0]
end

-- Bail if client did not send an Origin
-- for example, it may be a regular OPTIONS request that is not a CORS preflight request
if origin == nil or origin == '' then
return
end

transaction_data["origin"] = origin
transaction_data["allowed_methods"] = allowed_methods
transaction_data["allowed_origins"] = allowed_origins
transaction_data["allowed_headers"] = allowed_headers

txn:set_priv(transaction_data)

local method = txn.sf:method()
transaction_data["method"] = method

if method == "OPTIONS" and txn.reply ~= nil then
preflight_request_ver2(txn, origin, allowed_methods, allowed_origins, allowed_headers)
end
end

-- When invoked during a response, sets CORS headers so that the browser can read the response from permitted domains.
-- txn: The current transaction object that gives access to response properties.
function cors_response(txn)
local transaction_data = txn:get_priv()

if transaction_data == nil then
return
end

local origin = transaction_data["origin"]
local allowed_origins = transaction_data["allowed_origins"]
local allowed_methods = transaction_data["allowed_methods"]
local allowed_headers = transaction_data["allowed_headers"]
local method = transaction_data["method"]

-- Bail if client did not send an Origin
if origin == nil or origin == '' then
return
end

local allowed_origin = M.get_allowed_origin(origin, allowed_origins)

if allowed_origin == nil then
core.Debug("CORS: " .. origin .. " not allowed")
else
if method == "OPTIONS" and txn.reply == nil then
preflight_request_ver1(txn, allowed_methods, allowed_headers)
end

core.Debug("CORS: " .. origin .. " allowed")
txn.http:res_set_header("Access-Control-Allow-Origin", allowed_origin)

if allowed_origin ~= "*" then
txn.http:res_add_header("Vary", "Accept-Encoding,Origin")
end
end
end

-- Register the actions with HAProxy
core.register_action("cors", {"http-req"}, cors_request, 3)
core.register_action("cors", {"http-res"}, cors_response, 0)

return M
68 changes: 68 additions & 0 deletions .compose/haproxy/haproxy.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
global
user haproxy
group haproxy
# lua-load /usr/local/etc/haproxy/synapse_worker_endpoint_indexer.lua
lua-load /usr/local/etc/haproxy/cors.lua
log stdout format raw local0

defaults
log global
mode http
option httplog clf
option dontlognull
retries 3
option redispatch
maxconn 2000

frontend http-in
bind *:443 ssl crt /etc/ssl/certs/both.pem
http-request lua.cors "GET,PUT,POST,DELETE" "*" "*"
http-response lua.cors

acl is_matrix hdr_end(host) -i matrix.docker.localhost
acl is_synapse_matrix path_beg -i /_matrix
acl is_synapse_synapse path_beg -i /_synapse
acl is_well-known path_beg -i /.well-known/matrix
# If asking a synapse endpoint
#use_backend %[lua.path_to_worker] if is_matrix is_synapse_matrix || is_matrix is_synapse_synapse
# If not a /_matrix/ call, redirect to main worker
use_backend matrix if is_matrix

acl is_tom hdr_end(host) -i tom.docker.localhost
acl is_tom path_beg -i /_twake
use_backend tom if is_tom
# If it's asking for the well_known endpoint
use_backend tom if is_matrix is_well-known

acl is_chat hdr_end(host) -i chat.docker.localhost
use_backend chat if is_chat

acl is_portal hdr_end(host) -i docker.localhost
acl is_portal hdr_end(host) -i auth.docker.localhost
use_backend portal if is_portal

backend portal
balance roundrobin
option forwardfor
http-request set-header X-Forwarded-Proto https
server node1 auth:80 check
server node2 auth:80 check
server node3 auth:80 check

backend matrix
balance roundrobin
option forwardfor
http-request set-header X-Forwarded-Proto https
server synapse_main synapse:8008 check

backend tom
balance roundrobin
option forwardfor
http-request set-header X-Forwarded-Proto https
server node1 tom.server:3000 check

backend chat
balance roundrobin
option forwardfor
http-request set-header X-Forwarded-Proto https
server node1 chat:6868 check
Loading

0 comments on commit 427dba0

Please sign in to comment.