syntropy 0.3 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -2
  3. data/CHANGELOG.md +14 -0
  4. data/README.md +30 -11
  5. data/TODO.md +135 -0
  6. data/bin/syntropy +3 -3
  7. data/cmd/setup/template/site/.gitignore +57 -0
  8. data/cmd/setup/template/site/Dockerfile +32 -0
  9. data/cmd/setup/template/site/Gemfile +3 -0
  10. data/cmd/setup/template/site/README.md +0 -0
  11. data/cmd/setup/template/site/bin/console +0 -0
  12. data/cmd/setup/template/site/bin/restart +0 -0
  13. data/cmd/setup/template/site/bin/server +0 -0
  14. data/cmd/setup/template/site/bin/start +0 -0
  15. data/cmd/setup/template/site/bin/stop +0 -0
  16. data/cmd/setup/template/site/docker-compose.yml +51 -0
  17. data/cmd/setup/template/site/proxy/Dockerfile +5 -0
  18. data/cmd/setup/template/site/proxy/etc/Caddyfile +7 -0
  19. data/cmd/setup/template/site/proxy/etc/tls_auto +2 -0
  20. data/cmd/setup/template/site/proxy/etc/tls_cloudflare +4 -0
  21. data/cmd/setup/template/site/proxy/etc/tls_custom +1 -0
  22. data/cmd/setup/template/site/proxy/etc/tls_selfsigned +1 -0
  23. data/cmd/setup/template/site/site/_layout/default.rb +11 -0
  24. data/cmd/setup/template/site/site/about.md +6 -0
  25. data/cmd/setup/template/site/site/articles/cage.rb +29 -0
  26. data/cmd/setup/template/site/site/articles/index.rb +3 -0
  27. data/cmd/setup/template/site/site/assets/css/style.css +40 -0
  28. data/cmd/setup/template/site/site/assets/img/syntropy.png +0 -0
  29. data/cmd/setup/template/site/site/index.rb +15 -0
  30. data/docker-compose.yml +51 -0
  31. data/lib/syntropy/app.rb +112 -134
  32. data/lib/syntropy/errors.rb +16 -2
  33. data/lib/syntropy/file_watch.rb +5 -4
  34. data/lib/syntropy/module.rb +26 -5
  35. data/lib/syntropy/request_extensions.rb +96 -0
  36. data/lib/syntropy/router.rb +208 -0
  37. data/lib/syntropy/rpc_api.rb +26 -9
  38. data/lib/syntropy/side_run.rb +46 -0
  39. data/lib/syntropy/version.rb +1 -1
  40. data/lib/syntropy.rb +15 -49
  41. data/syntropy.gemspec +1 -1
  42. data/test/app/baz.rb +3 -0
  43. data/test/app_custom/_site.rb +3 -0
  44. data/test/test_app.rb +96 -51
  45. data/test/test_file_watch.rb +4 -4
  46. data/test/test_router.rb +90 -0
  47. data/test/test_side_run.rb +43 -0
  48. data/test/test_validation.rb +1 -1
  49. metadata +34 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d7686b3b9f5f09c819bebb9e46fdceb45c8c0a66441b1e12b130fa376defc5
4
- data.tar.gz: f616e5dcdedc8a08fb23d5a52c1a92296cd348f8555fede1ac31ba0859c1be28
3
+ metadata.gz: 7f125135bc3ed83c1050467241f48caf366600fead1d75db79455038e42e311a
4
+ data.tar.gz: ee9bd2bbf906eea9c90481ecdf643126285f65f63fe66a3f2b4f3050893e7c28
5
5
  SHA512:
6
- metadata.gz: 1f6d14df3cbbaa74d9ec897cb50fd35caed0f48b6410f2abb13fad79947b50bbf33c32aa6358ab6464ac5b90e861fffacb345805d487cd67f5bd77b36cd7295b
7
- data.tar.gz: af07c19a78d94fe37403246b302f95f047aeb12c764e8914e43f5ed1e1b1245cc0678a01e964e895fdc7aef0e775194c6ee9744939113352db07eca166693cc1
6
+ metadata.gz: 0bdce31c490701521d73829c02e1193fcd0da399b1e6186066ad5d6ecfa067dc143941300bf670fa4b447d5d5c177e903fa273d9d6d5bbfa2319ec0c74fd2678
7
+ data.tar.gz: 7b5eddf105f8654f379bf1d7f52948b713ee342dbd97eb1896e28b618b442ccf33c716d27a7f67c21cfd2c65f610b5c9677cd5e4faa0e777b8f727552628865c
data/.rubocop.yml CHANGED
@@ -7,8 +7,16 @@ AllCops:
7
7
  - "test/**/*.rb"
8
8
  - "examples/**/*.rb"
9
9
  - "Gemfile*"
10
- # Style/LambdaCall:
11
- # Enabled: false
10
+ Style/LambdaCall:
11
+ Enabled: false
12
+
13
+ Style/BlockDelimiters:
14
+ Enabled: false
15
+
16
+ Style/Semicolon:
17
+ Exclude:
18
+ - cmd/setup/template/site/site/articles/cage.rb
19
+
12
20
  # Style/ModuleFunction:
13
21
  # Enabled: false
14
22
  # Style/RegexpLiteral:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.5 2025-07-05
2
+
3
+ - Refactor App class to use Router
4
+ - Refactor routing functionality into separate Router class
5
+ - Add support for _site.rb file
6
+
7
+ ## 0.4 2025-07-03
8
+
9
+ - Improve errors API
10
+ - Add HTTP method validation
11
+ - Refactor Qeweney::Request extensions
12
+ - Add side_run API for running tasks on a side thread
13
+ - Add support for rendering markdown with layout
14
+
1
15
  ## 0.3 2025-06-25
2
16
 
3
17
  - Implement module reloading on file change
data/README.md CHANGED
@@ -22,22 +22,42 @@
22
22
  | Syntropy: A tendency towards complexity, structure, order, organization of
23
23
  ever more advantageous and orderly patterns.
24
24
 
25
- Syntropy is a WIP web framework for building multi-page and single-page apps.
25
+ Syntropy is a web framework for building multi-page and single-page apps.
26
26
  Syntropy uses file tree-based routing, and provides controllers for a number of
27
27
  common patterns, such as a SPA with client-side rendering, a standard
28
28
  server-rendered MPA, a REST API etc.
29
29
 
30
+ Syntropy also provides tools for working with lists of items represented as
31
+ files (ala Jekyll and other static site generators), allowing you to build
32
+ read-only apps (such as a markdown blog) without using a database.
33
+
34
+ For interactive apps, Syntropy provides basic tools for working with SQLite
35
+ databases in a concurrent environment.
36
+
37
+ Syntropy is based on:
38
+
39
+ - [UringMachine](https://github.com/digital-fabric/uringmachine) - a lean mean
40
+ [io_uring](https://unixism.net/loti/what_is_io_uring.html) machine for Ruby.
41
+ - [TP2](https://github.com/noteflakes/tp2) - an io_uring-based web server for
42
+ concurrent Ruby apps.
43
+ - [Qeweney](https://github.com/digital-fabric/qeweney) a uniform interface for
44
+ working with HTTP requests and responses.
45
+ - [Papercraft](https://github.com/digital-fabric/papercraft) HTML templating
46
+ with plain Ruby.
47
+ - [Extralite](https://github.com/digital-fabric/extralite) a fast and innovative
48
+ SQLite wrapper for Ruby.
49
+
30
50
  ## Routing
31
51
 
32
- Routing is performed automatically by following the tree structure of the
33
- Syntropy app. A simple example:
52
+ Syntropy routes request by following the tree structure of the Syntropy app. A
53
+ simple example:
34
54
 
35
55
  ```
36
56
  site/
37
57
  ├ _layout/
38
58
  | └ default.rb
39
59
  ├ _articles/
40
- | └ 2025-06-01-hello_world.md
60
+ | └ 2025-01-01-hello_world.md
41
61
  ├ api/
42
62
  | └ v1.rb
43
63
  ├ assets/
@@ -50,9 +70,8 @@ site/
50
70
  └ robots.txt
51
71
  ```
52
72
 
53
- The routing follows the file hierarchy, and Syntropy knows how to serve static
54
- asset files (CSS, JS, images...) as well as render markdown files and run custom
55
- Ruby code.
73
+ Syntropy knows how to serve static asset files (CSS, JS, images...) as well as
74
+ render markdown files and run modules written in Ruby.
56
75
 
57
76
  ## What does a Syntropic Ruby module look like?
58
77
 
@@ -67,7 +86,7 @@ def articles
67
86
  Syntropy.stamped_file_entries('/_articles')
68
87
  end
69
88
 
70
- @@layout.apply(title: 'archive') {
89
+ export @@layout.apply(title: 'archive') {
71
90
  div {
72
91
  ul {
73
92
  articles.each { |article|
@@ -98,8 +117,8 @@ class APIV1 < Syntropy::RPCAPI
98
117
  end
99
118
  end
100
119
 
101
- APIV1.new(Syntropy.env.open_db)
120
+ export APIV1
102
121
  ```
103
122
 
104
- Basically, the return value of the module is a template or a resource that
105
- responds to the request.
123
+ Basically, the exported value can be a template, a callable or a class that
124
+ responds to the request.
data/TODO.md CHANGED
@@ -0,0 +1,135 @@
1
+ - Refactor routing code into a separate Router class.
2
+ - The Router class is in charge of:
3
+ - caching routes
4
+ - loading modules
5
+ - unloading modules on file change
6
+ - calculating middleware for routes
7
+ - middleware is defined in `_hook.rb` modules
8
+ - interface: ->(req, next)
9
+ - a special case for handling errors is `_error.rb`
10
+ - interface: ->(req, err)
11
+ - dispatching routes
12
+ - error handling:
13
+ - on uncaught error, if an `_error.rb` file exists in the same directory
14
+ or up the file tree
15
+ - middleware:
16
+ - a closure is created from the composition of the different hooks
17
+ defined, from the route's directory and up the file
18
+ - error handlers and middleware closures are cached as part of the route's
19
+ entry
20
+ - on file change for any _hook.rb or _error.rb files, all route entries in
21
+ the corresponding subtree are invalidated
22
+
23
+
24
+ - Middleware
25
+
26
+ ```Ruby
27
+ # site/_hook.rb
28
+ export ->(req, &app) do
29
+ app.call(req)
30
+ rescue Syntropy::Error => e
31
+ render_error_page(req, e.http_status)
32
+ end
33
+
34
+ # an alternative, at least for errors is a _error.rb file:
35
+ # site/_error.rb
36
+ # Just a normal callable:
37
+ #
38
+ export ->(req, err) do
39
+ render_error_page(req, err.http_status)
40
+ end
41
+
42
+ # a _site.rb file can be used to wrap a whole app
43
+ # site/_site.rb
44
+
45
+ # this means we route according to the host header, with each
46
+ export Syntropy.route_by_host
47
+
48
+ # we can also rewrite requests:
49
+ rewriter = Syntropy
50
+ .select { it.host =~ /^tolkora\.(org|com)$/ }
51
+ .terminate { it.redirect_permanent('https://tolkora.net') }
52
+
53
+ # This is actuall a pretty interesting DSL design:
54
+ # a chain of operations that compose functions. So, we can select a
55
+ export rewriter.wrap(default_app)
56
+
57
+ # composing
58
+ export rewriter.wrap(Syntropy.some_custom_app.wrap(app))
59
+
60
+ # or maybe
61
+ export rewriter << some_other_middleware << app
62
+ ```
63
+
64
+
65
+
66
+
67
+ - CLI tool for setting up a site repo:
68
+
69
+ ```bash
70
+ # clone a newly created repo
71
+ ~/repo$ git clone https://github.com/foo/bar
72
+ ...
73
+ ~/repo$ syntropy setup bar
74
+
75
+ (syntropy banner)
76
+
77
+ Setting up Syntropy project in /home/sharon/repo/bar:
78
+
79
+ bar/
80
+ bin/
81
+ start
82
+ stop
83
+ restart
84
+ console
85
+ server
86
+ docker-compose.yml
87
+ Dockerfile
88
+ Gemfile
89
+ proxy/
90
+
91
+ README.md
92
+ site/
93
+ _layout/
94
+ default.rb
95
+ _lib/
96
+ about.md
97
+ articles/
98
+ long-form.md
99
+ assets/
100
+ js/
101
+ css/
102
+ style.css
103
+ img/
104
+ syntropy.png
105
+ index.rb
106
+ ```
107
+
108
+ - Some of the files might need templating, but we can maybe do without, or at
109
+ least make it as generic as possible.
110
+
111
+ - `syntropy setup` steps:
112
+
113
+ 1. Verify existence of target directory
114
+ 2. Copy files from Syntropy template to target directory
115
+ 3. Do chmod +x for bin/*
116
+ 4. Do bundle install in the target directory
117
+ 5. Show some information with regard to how to get started working with the
118
+ repo
119
+
120
+ - `syntropy provision` steps:
121
+
122
+ 1. Verify Ubuntu 22.x or higher
123
+ 2. Install git, docker, docker-compose
124
+
125
+ - `syntropy deploy` steps:
126
+
127
+ 1. Verify no uncommitted changes.
128
+ 2. SSH to remote machine.
129
+ 2.1. If not exists, clone repo
130
+ 2.2. Otherwise, verify remote machine repo is on same branch as local repo
131
+ 2.3. Do a git pull (what about credentials?)
132
+ 2.4. If gem bundle has changed, do a docker compose build
133
+ 2.5. If docker compose services are running, restart
134
+ 2.6. Otherwise, start services
135
+ 2.7. Verify service is running correctly
data/bin/syntropy CHANGED
@@ -57,9 +57,9 @@ if !File.directory?(opts[:location])
57
57
  end
58
58
 
59
59
 
60
-
61
- opts[:machine] = UM.new
60
+ # We set Syntropy.machine so we can reference it from anywhere
61
+ opts[:machine] = Syntropy.machine = UM.new
62
62
  opts[:logger] = opts[:logger] && TP2::Logger.new(opts[:machine], **opts)
63
63
 
64
- app = Syntropy::App.new(opts[:machine], opts[:location], '/', opts)
64
+ app = Syntropy::App.load(opts)
65
65
  TP2.run(opts) { app.call(it) }
@@ -0,0 +1,57 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
57
+ log
@@ -0,0 +1,32 @@
1
+ ARG RUBY_BASE_IMAGE=ruby:3.4.1-alpine
2
+ ARG GEM_CACHE_IMAGE=${RUBY_BASE_IMAGE}
3
+
4
+ # base image
5
+ FROM ${RUBY_BASE_IMAGE} AS base
6
+ RUN apk add --update sqlite-dev openssl-dev tzdata bash curl zip git
7
+ RUN apk add --update build-base
8
+ RUN gem install bundler:2.6.9
9
+
10
+ # gem cache
11
+ FROM ${GEM_CACHE_IMAGE} AS gem-cache
12
+ RUN mkdir -p /usr/local/bundle
13
+
14
+ FROM base AS gems
15
+ COPY --from=gem-cache /usr/local/bundle /usr/local/bundle
16
+ COPY Gemfile Gemfile.lock ./
17
+ RUN bundle install --jobs=4 --retry=5
18
+
19
+ # Final backend image
20
+ FROM base AS deploy
21
+
22
+ RUN adduser -D app
23
+ RUN chown app:app /home/app
24
+ WORKDIR /home/app
25
+ USER app
26
+
27
+ RUN mkdir -p /tmp
28
+ COPY --from=gems --chown=app:app /usr/local/bundle /usr/local/bundle
29
+
30
+ EXPOSE 1234
31
+
32
+ CMD ["bundle", "exec", "tp2", "."]
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'syntropy', '0.3'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,51 @@
1
+ services:
2
+ backend:
3
+ build: .
4
+ privileged: true
5
+ restart: always
6
+ ports:
7
+ - 127.0.0.1:1234:1234
8
+ # expose:
9
+ # - 1234
10
+ volumes:
11
+ - .:/home/app
12
+ deploy:
13
+ # replicas: 1
14
+ resources:
15
+ limits:
16
+ memory: 500M
17
+ # restart: unless-stopped
18
+ logging:
19
+ driver: "json-file"
20
+ options:
21
+ max-size: "1M"
22
+ max-file: "10"
23
+
24
+ # healthcheck:
25
+ # test: "curl 'http://localhost:1234/?q=ping'"
26
+ # interval: "30s"
27
+ # timeout: "3s"
28
+ # start_period: "5s"
29
+ # retries: 3
30
+
31
+ proxy:
32
+ depends_on:
33
+ - backend
34
+ build:
35
+ context: ./proxy
36
+ dockerfile: Dockerfile
37
+ restart: always
38
+ volumes:
39
+ - ./proxy/etc/Caddyfile:/etc/caddy/Caddyfile
40
+ ports:
41
+ - "80:80"
42
+ - "443:443"
43
+ - "443:443/udp"
44
+ # env_file:
45
+ # - ./conf/caddy.env
46
+ # - ./conf/caddy_sensitive.env
47
+ logging:
48
+ driver: "json-file"
49
+ options:
50
+ max-size: "1M"
51
+ max-file: "10"
@@ -0,0 +1,5 @@
1
+ FROM caddy:2.10.0-builder AS builder
2
+ RUN xcaddy build
3
+
4
+ FROM caddy:2.10.0
5
+ COPY --from=builder /usr/bin/caddy /usr/bin/caddy
@@ -0,0 +1,7 @@
1
+ localhost {
2
+ reverse_proxy noteflakescom-backend-1:1234
3
+ }
4
+
5
+ my-awesome-domain.com {
6
+ reverse_proxy noteflakescom-backend-1:1234
7
+ }
@@ -0,0 +1,2 @@
1
+ tls {$TLS_AUTO_EMAIL}
2
+
@@ -0,0 +1,4 @@
1
+ tls {
2
+ dns cloudflare {env.CLOUDFLARE_API_TOKEN}
3
+ }
4
+
@@ -0,0 +1 @@
1
+ tls {$TLS_CUSTOM_CERT} {$TLS_CUSTOM_KEY}
@@ -0,0 +1 @@
1
+ tls internal
@@ -0,0 +1,11 @@
1
+ export templ { |*a, **b|
2
+ html {
3
+ head {
4
+ title 'My awesome Syntropy website'
5
+ link rel: 'stylesheet', type: 'text/css', href: '/assets/css/style.css'
6
+ }
7
+ body {
8
+ emit_yield(*a, **b)
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ # About my site
5
+
6
+ Lorem ipsum my awesome site.
@@ -0,0 +1,29 @@
1
+ layout = import('_layout/default')
2
+
3
+ poem = [
4
+ " in ten\xA0", 'M', 'inutes',
5
+ ' ', 'C', 'ome back: you will',
6
+ 'have taught me chi', 'N', 'ese',
7
+ ' (s', 'A', 'tie).',
8
+ ' shall I ret', 'U', 'rn the favor?',
9
+ ' ', 'G', 'ive you',
10
+ ' ot', 'H', 'er lessons',
11
+ ' (', 'T', 'ing!)?',
12
+ ' ', 'O', 'r would you prefer',
13
+ ' sile', 'N', 'ce?',
14
+ ]
15
+
16
+ export layout.apply {
17
+ article(class: 'mesostic') {
18
+ h2 'For William McN. who studied with Ezra Pound'
19
+
20
+ content {
21
+ span(_for: poem) { text it }
22
+ }
23
+
24
+ author {
25
+ span "-\xA0"
26
+ a 'John cage', href: 'https://en.wikipedia.org/wiki/John_Cage'
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export templ {
2
+ h1 'Articles'
3
+ }
@@ -0,0 +1,40 @@
1
+ body {
2
+ font-family: sans-serif;
3
+ }
4
+
5
+ article.mesostic {
6
+ width: 600px;
7
+ margin: 2em auto;
8
+ font-size: 1.4em;
9
+
10
+ * {
11
+ font-family: monospace;
12
+ }
13
+
14
+ h2 {
15
+ text-align: center;
16
+ }
17
+
18
+ content {
19
+ display: grid;
20
+ margin: 2em 0;
21
+ grid-template-columns: 1fr auto 1fr;
22
+
23
+ span {
24
+ display: inline-block;
25
+ }
26
+ span:nth-child(3n + 1) {
27
+ text-align: right;
28
+ }
29
+
30
+ span:nth-child(3n + 2) {
31
+ text-align: center;
32
+ font-weight: bold;
33
+ }
34
+ }
35
+
36
+ author {
37
+ display: block;
38
+ text-align: right;
39
+ }
40
+ }
@@ -0,0 +1,15 @@
1
+ layout = import('_layout/default')
2
+
3
+ export layout.apply {
4
+ h1 'Hello from Syntropy'
5
+ p {
6
+ span "Here's an "
7
+ a 'about', href: 'about'
8
+ span ' page.'
9
+ }
10
+ p {
11
+ span "Here's an "
12
+ a 'article', href: 'articles/cage'
13
+ span ' page.'
14
+ }
15
+ }
@@ -0,0 +1,51 @@
1
+ services:
2
+ backend:
3
+ build: .
4
+ privileged: true
5
+ restart: always
6
+ ports:
7
+ - 127.0.0.1:1234:1234
8
+ # expose:
9
+ # - 1234
10
+ volumes:
11
+ - .:/home/app
12
+ deploy:
13
+ # replicas: 1
14
+ resources:
15
+ limits:
16
+ memory: 500M
17
+ # restart: unless-stopped
18
+ logging:
19
+ driver: "json-file"
20
+ options:
21
+ max-size: "1M"
22
+ max-file: "10"
23
+
24
+ # healthcheck:
25
+ # test: "curl 'http://localhost:1234/?q=ping'"
26
+ # interval: "30s"
27
+ # timeout: "3s"
28
+ # start_period: "5s"
29
+ # retries: 3
30
+
31
+ proxy:
32
+ depends_on:
33
+ - backend
34
+ build:
35
+ context: ./proxy
36
+ dockerfile: Dockerfile
37
+ restart: always
38
+ volumes:
39
+ - ./proxy/etc/Caddyfile:/etc/caddy/Caddyfile
40
+ ports:
41
+ - "80:80"
42
+ - "443:443"
43
+ - "443:443/udp"
44
+ # env_file:
45
+ # - ./conf/caddy.env
46
+ # - ./conf/caddy_sensitive.env
47
+ logging:
48
+ driver: "json-file"
49
+ options:
50
+ max-size: "1M"
51
+ max-file: "10"