Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2093f67a64
|
||
|
|
695977172f
|
||
|
|
bc37df52a0
|
||
|
|
6279699c11
|
||
|
|
0d9c580a0a
|
||
|
|
49c2c9feea
|
||
|
|
465563d6a6
|
||
|
|
911d2a5855
|
||
|
|
d8bba02636
|
||
|
|
2419982ef5
|
||
|
|
8fae099c12
|
||
|
|
4536b6505c
|
No files matched your search
@@ -7,13 +7,16 @@ ejabberd, Discourse, Mastodon, remoteStorage, Nostr, LNDHub, and BTCPay.
|
||||
## Development environment
|
||||
|
||||
Development runs in Docker Compose — run all commands against the `web` container.
|
||||
Start services: `docker compose up` (web, ldap, redis, minio, liquor-cabinet, strfry).
|
||||
Start services: `docker compose up` (web, ldap, redis, garage, liquor-cabinet, strfry).
|
||||
The `web` service runs `bin/dev` (foreman: Puma + Tailwind CSS watcher) and embeds
|
||||
Solid Queue workers (`SOLID_QUEUE_IN_PUMA=true`).
|
||||
|
||||
First-time LDAP setup (after creating the 389ds backend once):
|
||||
`docker compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be_name="dev"`
|
||||
then `docker compose run web bin/rails ldap:setup`.
|
||||
First-time LDAP and database setup is automated: the `ldap-init` service
|
||||
creates the 389ds back-end, then the `web` entrypoint seeds LDAP and the
|
||||
databases on first start and runs `db:prepare` on every boot. Manual
|
||||
equivalents: `docker compose exec ldap dsconf localhost backend create
|
||||
--suffix="dc=kosmos,dc=org" --be-name="dev"` and `docker compose run --rm web
|
||||
bin/rails ldap:setup`.
|
||||
|
||||
## Common commands (prefix with `docker compose exec web`)
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@ so:
|
||||
|
||||
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
|
||||
Docker Desktop)
|
||||
3. Run `docker compose up --build` and wait until all services have started
|
||||
2. Run `docker compose up --build` and wait until all services have started
|
||||
(389ds might take an extra minute to be ready). This will take a while when
|
||||
running for the first time, so you might want to do something else in the
|
||||
meantime.
|
||||
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
|
||||
5. `docker compose run web rails ldap:setup`
|
||||
6. `docker compose run web rails db:setup`
|
||||
|
||||
On the first start, the `ldap-init` service creates the 389ds back-end, and the
|
||||
`web` container then seeds the LDAP directory and the databases automatically.
|
||||
On every start, `web` also applies any pending database migrations.
|
||||
|
||||
After these steps, you should have a working Rails app with a handful of test
|
||||
users running on [http://localhost:3000](http://localhost:3000).
|
||||
@@ -71,15 +72,12 @@ containers you want to run to the `up` command, like so:
|
||||
|
||||
#### LDAP server
|
||||
|
||||
After creating the Docker container for the first time (or after deleting it),
|
||||
you need to run the following command once, in order to create the dirsrv
|
||||
back-end:
|
||||
On first start, the `ldap-init` service creates the dirsrv back-end
|
||||
automatically, and the `web` container then seeds it with development entries.
|
||||
To do either step manually (for example, after changing the setup), run:
|
||||
|
||||
docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"
|
||||
|
||||
Now you can seed the back-end with data using this Rails task:
|
||||
|
||||
bundle exec rails ldap:setup
|
||||
docker compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"
|
||||
docker compose run --rm web bin/rails ldap:setup
|
||||
|
||||
The setup task will first delete any existing entries in the directory tree
|
||||
("dc=kosmos,dc=org"), and then create our development entries.
|
||||
@@ -88,20 +86,42 @@ Note that all 389ds data is stored in the `389ds-data` volume. So if you want
|
||||
to start over with a fresh installation, delete both that volume as well as the
|
||||
container.
|
||||
|
||||
#### Minio / remoteStorage
|
||||
To reset the development environment completely, remove all volumes plus the
|
||||
generated database files and the first-run marker, then start over:
|
||||
|
||||
If you want to run remoteStorage accounts locally, you will have to create the
|
||||
respective bucket first. With the `minio` container running (run by default
|
||||
when using Docker Compose), follow these steps:
|
||||
docker compose down -v
|
||||
rm -f db/*.sqlite3 tmp/.setup-complete
|
||||
docker compose up --build
|
||||
|
||||
* `docker compose up web redis minio liquor-cabinet`
|
||||
* Head to http://localhost:9001 and log in with user `minioadmin`, password
|
||||
`minioadmin`
|
||||
* Create a new bucket called `remotestorage` (or whatever you
|
||||
change the `S3_BUCKET` config to)
|
||||
* Create a new key with ID "dev-key" and secret "123456789" (or whatever you
|
||||
change `S3_ACCESS_KEY` and `S3_SECRET_KEY` to). Leave the policy field empty,
|
||||
as it will automatically allow access to the bucket you created.
|
||||
#### Garage / remoteStorage
|
||||
|
||||
remoteStorage accounts use the `garage` S3-compatible object store. On first
|
||||
start, Garage automatically configures a single-node cluster and creates the
|
||||
`remotestorage` bucket together with a `dev-key1` access key (secret
|
||||
`1234567890123456`), so no manual setup is required.
|
||||
|
||||
If you want to run remoteStorage accounts locally, the `garage` container is
|
||||
started by default when using Docker Compose. To run just the remoteStorage
|
||||
stack:
|
||||
|
||||
* `docker compose up web redis garage liquor-cabinet`
|
||||
|
||||
The S3 API is available at http://localhost:3900 (region `garage`). If you want
|
||||
to start over with a fresh storage, delete the `garage-data` volume as well as
|
||||
the container.
|
||||
|
||||
#### Accessing remoteStorage from another machine
|
||||
|
||||
remoteStorage clients force HTTPS for any host except `localhost`, and browsers
|
||||
block plain-HTTP requests to a LAN IP as mixed content. To connect to the dev
|
||||
remoteStorage from a browser on another machine (including production apps such
|
||||
as Inspektor), forward the ports over SSH and connect as `localhost`:
|
||||
|
||||
ssh -N -L 3000:localhost:3000 -L 4567:localhost:4567 <user>@<dev-host>
|
||||
|
||||
Then use `<user>@localhost:3000` as the remoteStorage address in the client.
|
||||
WeFinger discovery and storage requests are served through the forwarded ports,
|
||||
so no TLS setup is needed.
|
||||
|
||||
### Adding npm modules to use with Stimulus controllers
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Services::RemotestorageController < Services::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :require_service_available
|
||||
before_action :require_feature_enabled
|
||||
before_action :require_service_enabled
|
||||
|
||||
# Dashboard
|
||||
def show
|
||||
@@ -17,8 +17,8 @@ class Services::RemotestorageController < Services::BaseController
|
||||
http_status :not_found unless Setting.remotestorage_enabled?
|
||||
end
|
||||
|
||||
def require_feature_enabled
|
||||
unless Flipper.enabled?(:remotestorage, current_user)
|
||||
def require_service_enabled
|
||||
unless current_user.service_enabled?(:remotestorage)
|
||||
http_status :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
class Services::RsAuthsController < Services::BaseController
|
||||
before_action :authenticate_user!
|
||||
before_action :require_feature_enabled
|
||||
before_action :require_service_enabled
|
||||
before_action :require_service_available
|
||||
# before_action :require_service_enabled
|
||||
before_action :find_rs_auth, only: [:destroy, :launch_app]
|
||||
|
||||
def index
|
||||
@@ -23,9 +22,12 @@ class Services::RsAuthsController < Services::BaseController
|
||||
end
|
||||
|
||||
def launch_app
|
||||
user_address = Rails.env.development? ?
|
||||
"#{current_user.cn}@localhost:3000" :
|
||||
current_user.address
|
||||
user_address =
|
||||
if Rails.env.development?
|
||||
"#{current_user.cn}@#{request.host_with_port}"
|
||||
else
|
||||
current_user.address
|
||||
end
|
||||
|
||||
launch_url = "#{@auth.launch_url}#remotestorage=#{user_address}"
|
||||
|
||||
@@ -34,8 +36,8 @@ class Services::RsAuthsController < Services::BaseController
|
||||
|
||||
private
|
||||
|
||||
def require_feature_enabled
|
||||
unless Flipper.enabled?(:remotestorage, current_user)
|
||||
def require_service_enabled
|
||||
unless current_user.service_enabled?(:remotestorage)
|
||||
http_status :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,8 +88,14 @@ class WebfingerController < WellKnownController
|
||||
end
|
||||
|
||||
def remotestorage_link
|
||||
auth_url = new_rs_oauth_url(@username, host: Setting.rs_accounts_domain)
|
||||
storage_url = "#{Setting.rs_storage_url}/#{@username}"
|
||||
auth_url =
|
||||
if Rails.env.development?
|
||||
new_rs_oauth_url(@username)
|
||||
else
|
||||
new_rs_oauth_url(@username, host: Setting.rs_accounts_domain)
|
||||
end
|
||||
|
||||
storage_url = "#{remotestorage_storage_base_url}/#{@username}"
|
||||
|
||||
{
|
||||
rel: "http://tools.ietf.org/id/draft-dejong-remotestorage",
|
||||
@@ -103,4 +109,12 @@ class WebfingerController < WellKnownController
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def remotestorage_storage_base_url
|
||||
return Setting.rs_storage_url unless Rails.env.development?
|
||||
|
||||
uri = URI.parse(Setting.rs_storage_url)
|
||||
uri.host = request.host
|
||||
uri.to_s
|
||||
end
|
||||
end
|
||||
@@ -305,7 +305,7 @@
|
||||
<td>remoteStorage</td>
|
||||
<td>
|
||||
<%= render FormElements::ToggleComponent.new(
|
||||
enabled: Flipper.enabled?(:remotestorage, current_user) && @services_enabled.include?("remotestorage"),
|
||||
enabled: @services_enabled.include?("remotestorage"),
|
||||
input_enabled: false
|
||||
) %>
|
||||
</td>
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if Setting.remotestorage_enabled? &&
|
||||
Flipper.enabled?(:remotestorage, current_user) %>
|
||||
<% if Setting.remotestorage_enabled? %>
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
|
||||
bg-[url(/img/logos/icon_remotestorage.svg)]">
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
active: @settings_section.to_s == "lightning"
|
||||
) %>
|
||||
<% end %>
|
||||
<% if Setting.remotestorage_enabled? &&
|
||||
Flipper.enabled?(:remotestorage, current_user) %>
|
||||
<% if Setting.remotestorage_enabled? %>
|
||||
<%= render SidenavLinkComponent.new(
|
||||
name: "Storage", path: setting_path(:remotestorage), icon: "remotestorage",
|
||||
active: @settings_section.to_s == "remotestorage"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
:concurrency: 2
|
||||
production:
|
||||
:concurrency: 10
|
||||
:queues:
|
||||
- default
|
||||
- mailers
|
||||
|
||||
+6
-3
@@ -1,8 +1,9 @@
|
||||
require 'sidekiq/testing'
|
||||
|
||||
ldap = LdapService.new
|
||||
|
||||
Sidekiq::Testing.inline! do
|
||||
original_queue_adapter = ActiveJob::Base.queue_adapter
|
||||
ActiveJob::Base.queue_adapter = :inline
|
||||
|
||||
begin
|
||||
ldap.delete_all_users!
|
||||
|
||||
puts "Create user: admin"
|
||||
@@ -25,4 +26,6 @@ Sidekiq::Testing.inline! do
|
||||
password: "user is user", confirmed: true
|
||||
})
|
||||
end
|
||||
ensure
|
||||
ActiveJob::Base.queue_adapter = original_queue_adapter
|
||||
end
|
||||
+40
-14
@@ -12,6 +12,20 @@ services:
|
||||
DS_DM_PASSWORD: passthebutter
|
||||
SUFFIX_NAME: "dc=kosmos,dc=org"
|
||||
|
||||
ldap-init:
|
||||
image: 4teamwork/389ds:latest
|
||||
networks:
|
||||
- internal_network
|
||||
volumes:
|
||||
- ./docker/ldap-init.sh:/ldap-init.sh:ro
|
||||
environment:
|
||||
LDAP_ADMIN_PASSWORD: passthebutter
|
||||
LDAP_SUFFIX: "dc=kosmos,dc=org"
|
||||
depends_on:
|
||||
ldap:
|
||||
condition: service_healthy
|
||||
command: ["/bin/sh", "/ldap-init.sh"]
|
||||
|
||||
redis:
|
||||
restart: always
|
||||
image: redis:7-alpine
|
||||
@@ -26,7 +40,7 @@ services:
|
||||
web:
|
||||
build: .
|
||||
tty: true
|
||||
command: bash -c "rm -f /akkounts/tmp/pids/server.pid; bin/dev"
|
||||
command: ["bash", "docker/web-entrypoint.sh"]
|
||||
volumes:
|
||||
- .:/akkounts
|
||||
- /akkounts/node_modules
|
||||
@@ -57,20 +71,28 @@ services:
|
||||
NOSTR_PRIVATE_KEY: 7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea
|
||||
NOSTR_RELAY_URL: ws://strfry:7777
|
||||
depends_on:
|
||||
- ldap
|
||||
- redis
|
||||
ldap:
|
||||
condition: service_started
|
||||
ldap-init:
|
||||
condition: service_completed_successfully
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
minio:
|
||||
image: quay.io/minio/minio:latest
|
||||
command: "server /data --console-address ':9001'"
|
||||
garage:
|
||||
image: dxflrs/garage:v2.4.1
|
||||
command: ["/garage", "server", "--single-node", "--default-bucket"]
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
- "3900:3900"
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
- ./docker/garage/garage.toml:/etc/garage.toml:ro
|
||||
- garage-data:/var/lib/garage
|
||||
environment:
|
||||
GARAGE_DEFAULT_ACCESS_KEY: dev-key1
|
||||
GARAGE_DEFAULT_SECRET_KEY: "1234567890123456"
|
||||
GARAGE_DEFAULT_BUCKET: remotestorage
|
||||
|
||||
liquor-cabinet:
|
||||
image: gitea.kosmos.org/5apps/liquor-cabinet:2.0.0-rc.1
|
||||
@@ -79,17 +101,21 @@ services:
|
||||
- internal_network
|
||||
ports:
|
||||
- "4567:4567"
|
||||
volumes:
|
||||
- ./docker/liquor-cabinet/rainbows.conf.rb:/etc/liquor-cabinet/rainbows.conf.rb:ro
|
||||
command: ["bundle", "exec", "rainbows", "-c", "/etc/liquor-cabinet/rainbows.conf.rb", "--listen", "0.0.0.0:4567"]
|
||||
environment:
|
||||
RACK_ENV: staging
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_DB: 1
|
||||
S3_ENDPOINT: http://minio:9000
|
||||
S3_ACCESS_KEY: dev-key
|
||||
S3_SECRET_KEY: 123456789
|
||||
S3_ENDPOINT: http://garage:3900
|
||||
S3_REGION: garage
|
||||
S3_ACCESS_KEY: dev-key1
|
||||
S3_SECRET_KEY: "1234567890123456"
|
||||
S3_BUCKET: remotestorage
|
||||
depends_on:
|
||||
- minio
|
||||
- garage
|
||||
- redis
|
||||
|
||||
strfry:
|
||||
@@ -127,7 +153,7 @@ networks:
|
||||
volumes:
|
||||
389ds-data:
|
||||
driver: local
|
||||
minio-data:
|
||||
garage-data:
|
||||
driver: local
|
||||
redis-data:
|
||||
driver: local
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
metadata_dir = "/var/lib/garage/meta"
|
||||
data_dir = "/var/lib/garage/data"
|
||||
db_engine = "sqlite"
|
||||
|
||||
replication_factor = 1
|
||||
|
||||
rpc_bind_addr = "[::]:3901"
|
||||
rpc_public_addr = "127.0.0.1:3901"
|
||||
rpc_secret = "1799bccfd7411eddcf9ebd316bc1f5287ad12a68094e1c6ac6abde7e6feae1ec"
|
||||
|
||||
[s3_api]
|
||||
s3_region = "garage"
|
||||
api_bind_addr = "[::]:3900"
|
||||
root_domain = ".s3.garage.localhost"
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
SUFFIX="${LDAP_SUFFIX:-dc=kosmos,dc=org}"
|
||||
URI="ldap://ldap:3389"
|
||||
|
||||
if dsconf -D "cn=Directory Manager" -w "$LDAP_ADMIN_PASSWORD" "$URI" \
|
||||
backend suffix list --suffix 2>/dev/null | grep -Fqx "$SUFFIX"; then
|
||||
echo "LDAP backend for $SUFFIX already exists, skipping."
|
||||
else
|
||||
echo "Creating LDAP backend for $SUFFIX..."
|
||||
dsconf -D "cn=Directory Manager" -w "$LDAP_ADMIN_PASSWORD" "$URI" \
|
||||
backend create --suffix "$SUFFIX" --be-name dev
|
||||
fi
|
||||
@@ -0,0 +1,5 @@
|
||||
Rainbows! do
|
||||
use :ThreadPool
|
||||
worker_connections 16
|
||||
client_max_body_size 100 * 1024 * 1024
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ ! -f tmp/.setup-complete ]; then
|
||||
echo "First start: setting up LDAP entries and databases..."
|
||||
bin/rails ldap:setup
|
||||
bin/rails db:setup
|
||||
touch tmp/.setup-complete
|
||||
else
|
||||
bin/rails db:prepare
|
||||
fi
|
||||
|
||||
rm -f tmp/pids/server.pid
|
||||
exec bin/dev
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
"postcss-preset-env": "^7.8.3",
|
||||
"tailwindcss": "^3.4.0"
|
||||
},
|
||||
"version": "0.11.0",
|
||||
"version": "0.11.1",
|
||||
"scripts": {
|
||||
"build:css:tailwind": "bun ./node_modules/tailwindcss/lib/cli.js --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||
"build:css": "bun run build:css:tailwind"
|
||||
|
||||
@@ -6,7 +6,9 @@ RSpec.describe Services::RsAuthsController, type: :controller do
|
||||
before do
|
||||
allow_any_instance_of(AppCatalog::WebApp).to receive(:update_metadata).and_return(true)
|
||||
allow_any_instance_of(RemoteStorageAuthorization).to receive(:remove_token_expiry_job).and_return(nil)
|
||||
allow_any_instance_of(Flipper).to receive(:enabled?).and_return(true)
|
||||
allow_any_instance_of(LdapService).to receive(:fetch_users).and_return([
|
||||
{ services_enabled: ["remotestorage"] }
|
||||
])
|
||||
end
|
||||
|
||||
describe "GET /services/storage/rs_auths/:id/launch_app" do
|
||||
@@ -35,6 +37,20 @@ RSpec.describe Services::RsAuthsController, type: :controller do
|
||||
expect(response).to redirect_to(launch_url)
|
||||
end
|
||||
end
|
||||
|
||||
context "when remoteStorage is not enabled for the user" do
|
||||
before do
|
||||
allow_any_instance_of(LdapService).to receive(:fetch_users).and_return([
|
||||
{ services_enabled: [] }
|
||||
])
|
||||
|
||||
get :launch_app, params: { id: 1 }
|
||||
end
|
||||
|
||||
it "responds with forbidden" do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,6 +11,7 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
end
|
||||
|
||||
describe "#create" do
|
||||
before(:each) { redis_rs_delete_keys("authorizations:*") }
|
||||
after(:each) { clear_enqueued_jobs }
|
||||
after(:all) { redis_rs_delete_keys("authorizations:*") }
|
||||
|
||||
@@ -27,10 +28,9 @@ RSpec.describe RemoteStorageAuthorization, type: :model do
|
||||
end
|
||||
|
||||
it "stores a token in redis" do
|
||||
user_auth_keys = redis_rs.keys("authorizations:#{user.cn}:*")
|
||||
expect(user_auth_keys.length).to eq(1)
|
||||
auth
|
||||
|
||||
authorizations = redis_rs.smembers(user_auth_keys.first)
|
||||
authorizations = redis_rs.smembers("authorizations:#{user.cn}:#{auth.token}")
|
||||
expect(authorizations.sort).to eq(%w(documents photos contacts:rw videos:r tasks/work:r).sort)
|
||||
end
|
||||
|
||||
|
||||
Reference in new issue
Block a user