Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch aleph
Excluding Merge-Ins
This is equivalent to a diff from
00de2fb20c
to 7d5a77599c
2024-02-11
| | |
21:20 |
|
Leaf
check-in: 7d5a77599c user: scstarkey tags: aleph
|
21:17 |
|
check-in: a94f0852b9 user: scstarkey tags: trunk
|
19:50 |
|
check-in: 30d4dfba91 user: scstarkey tags: aleph
|
18:15 |
|
check-in: b018440299 user: scstarkey tags: aleph
|
16:37 |
|
check-in: 00de2fb20c user: scstarkey tags: trunk
|
2024-02-08
| | |
17:33 |
|
check-in: 6c75bd0bdf user: scstarkey tags: trunk
|
| | |
Changes to containerfiles/mylife.containerfile.
1
2
3
4
5
6
7
8
9
10
11
|
1
2
3
4
5
6
7
8
9
10
11
|
-
+
|
FROM clojure:lein-2.10.0-alpine AS build
WORKDIR /root
COPY project.clj ./project.clj
RUN apk add --no-cache bash git
RUN apk add --no-cache bash git libstdc++
RUN lein deps
RUN lein with-profile dev,test,uberjar deps
COPY src ./src
COPY src-dev ./src-dev
COPY src-prod ./src-prod
COPY test ./test
|
︙ | | |
24
25
26
27
28
29
30
31
|
24
25
26
27
28
29
30
31
|
-
+
|
ENV HOT_RELOAD_ENABLED=false
RUN mkdir -p /usr/local/mylife/.server
RUN apt-get update && apt-get install -y fontconfig libfreetype6 && apt-get clean
COPY --from=build /root/target/uberjar/mylife.jar /usr/local/mylife
CMD java -XX:+UseZGC -XX:+ZGenerational -Xmx192m -jar /usr/local/mylife/mylife.jar
CMD java -XX:+UseZGC -XX:+ZGenerational -Xmx340m -jar /usr/local/mylife/mylife.jar
|
Changes to project.clj.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
+
-
|
(def slf4j-version "2.0.7")
(def acme4j-version "2.16")
(def reitit-version "0.6.0")
(def bouncy-version "1.75")
(defproject me.calmabiding.mylife "0.1.0-SNAPSHOT"
:description "My Life. On the Web. In your RSS reader."
:url "https://calmabiding.me/mylife"
:license {:name "Affero GNU GPL 3.0"
:url "https://www.gnu.org/licenses/agpl-3.0.en.html"}
:repositories {"jitpack" {:url "https://jitpack.io"}}
:dependencies [[org.clojure/clojure "1.11.1"]
[aleph "0.7.1"]
[clj-cron-parse "0.1.5"]
[clj-http "3.12.3"]
[clj-rss "0.4.0"]
[clojure.java-time "1.4.2"]
[clojure-humanize "0.2.2"]
[com.draines/postal "2.0.5"]
[com.fzakaria/slf4j-timbre "0.4.1"]
[com.github.seancorfield/next.jdbc "1.3.909"]
[com.github.multiformats/java-multibase "1.1.1"]
[com.taoensso/timbre "6.3.1"]
[com.vladsch.flexmark/flexmark-all "0.64.8"]
[com.xtdb/xtdb-core "1.24.3"]
[com.xtdb/xtdb-jdbc "1.24.3"]
[commons-io "2.15.1"]
[conman "0.9.6"]
[coreagile/defenv "3.0.0"]
[expound "0.9.0"]
[org.clj-commons/hickory "0.7.4"]
[info.sunng/ring-jetty9-adapter "0.31.1"]
[io.sunshower.arcus/arcus-identicon "1.41.50.Final"]
[jdbc-ring-session "1.5.3"]
[markdown-clj "1.11.8"]
[metosin/ring-http-response "0.9.3"]
[mount "0.1.17"]
[nano-id "1.1.0"]
[org.apache.xmlgraphics/batik-all "1.17"]
|
︙ | | |
Changes to src/mylife/config.clj.
︙ | | |
447
448
449
450
451
452
453
454
455
456
457
458
459
460
|
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
|
+
+
+
+
+
+
|
(defn server-url []
(let [{:keys [domain ssl-port]} (env)]
(if (= 443 ssl-port)
(format "https://%s" domain)
(format "https://%s:%s" domain ssl-port))))
(defn internal-server-url []
(let [{:keys [domain port]} (env)]
(if (= 80 port)
(format "http://%s" domain)
(format "http://%s:%s" domain port))))
(defn external-connect-url []
(or
(env :ext-url-override)
(env :external-url)
(server-url)))
(defn get-site-owner []
|
︙ | | |
Changes to src/mylife/core.clj.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
+
-
+
-
+
+
+
+
-
-
+
+
+
+
+
+
+
-
+
-
+
+
-
+
+
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
+
+
-
+
-
+
-
+
-
+
-
+
|
(ns mylife.core
(:require [aleph.http :as http]
(:require [clojure.java.io :as io]
[clojure.java.io :as io]
[jdbc-ring-session.cleaner :refer [start-cleaner stop-cleaner]]
[mount.core :as mount]
[mylife.activity :refer [generate-available-activity-email]]
[mylife.config :refer [cfg-db env external-connect-url] :as cfg]
[mylife.config :refer [cfg-db
env
external-connect-url
internal-server-url] :as cfg]
[mylife.data :as d]
[mylife.disk-safety :as s]
[mylife.email :as email]
[mylife.scheduler :as sched]
[mylife.ssl :as ssl]
[mylife.time :as time]
[mylife.web :as w]
[mylife.webmention :as wm]
[ring.adapter.jetty9 :as jetty]
[taoensso.timbre :as log])
(:gen-class)
(:import (java.io File)
(java.util UUID)
(org.eclipse.jetty.server Server)))
(io.netty.handler.ssl
ApplicationProtocolConfig
ApplicationProtocolConfig$Protocol
ApplicationProtocolConfig$SelectedListenerFailureBehavior
ApplicationProtocolConfig$SelectorFailureBehavior
ApplicationProtocolNames
SslContextBuilder)))
(set! *warn-on-reflection* true)
(Thread/setDefaultUncaughtExceptionHandler
(reify Thread$UncaughtExceptionHandler
(uncaughtException [_ thread ex]
(log/error {:what :uncaught-exception
:exception ex
:where (str "Uncaught exception on" (.getName thread))}))))
(defn- ssl-err [e keystore keystore-password-file]
(log/error
e
"Unable to load certificates. Cleaning up possibly corrupt files.
Will try again when you start the server again!")
(doseq [^File f [keystore keystore-password-file]]
(.delete f))
(System/exit 1))
(mount/defstate ^{:on-reload :noop} ssl-options
(mount/defstate ^{:on-reload :noop} keystore-fn
:start
(when (env :server-enabled?)
(let [cert-dir (doto (-> :cert-dir env io/file) s/guarantee-secure-dir!)
keystore (io/file cert-dir ".keystore")
keystore-password-file (io/file cert-dir ".keystore-password")
keystore-path (.getAbsolutePath keystore)]
keystore-path (.getAbsolutePath keystore)
key-password-fn (fn [] (slurp keystore-password-file))]
(if-not (and (.exists keystore)
(.exists keystore-password-file))
(try
(log/info "Generating self-signed SSL certificates")
(let [new-password (str (UUID/randomUUID))
{:keys [private-key cert]} (ssl/self-signed-cert (env :domain))]
(doseq [^File f [keystore keystore-password-file]]
(s/new-secure-file! f))
(spit keystore-password-file new-password)
(ssl/save-single-cert! keystore private-key cert new-password))
(catch Exception e
(ssl-err e keystore keystore-password-file)))
(try
(ssl/validate-keystore keystore (slurp keystore-password-file))
(ssl/validate-keystore keystore (key-password-fn))
(catch Exception e
(ssl-err e keystore keystore-password-file))))
(fn []
{:key-manager-factory
(ssl/load-key-manager-factory keystore-path (key-password-fn))
{:keystore keystore-path
:key-password-fn (fn [] (slurp keystore-password-file))}))))
:keystore-path keystore-path
:key-password-fn key-password-fn}))))
(mount/defstate ^{:on-reload :noop} jdbc-session-cleaner
:start
(start-cleaner cfg-db)
:stop
(stop-cleaner jdbc-session-cleaner))
(declare stop-server)
(mount/defstate ^{:on-reload :noop} ^Server http-server
(mount/defstate ^{:on-reload :noop} http-server
:start
(when (env :server-enabled?)
(let [handler (w/app cfg-db)]
(jetty/run-jetty
handler
(let [{:keys [keystore key-password-fn]} (ssl-options)]
(merge {:join? false, :ssl? true, :h2? true, :async? false
:keystore keystore, :key-password (key-password-fn)}
(env))))))
(let [handler (w/app cfg-db)
{:keys [port ssl-port]} (env)
^ApplicationProtocolConfig$SelectorFailureBehavior
selector-failure-behavior
ApplicationProtocolConfig$SelectorFailureBehavior/NO_ADVERTISE
^ApplicationProtocolConfig$SelectedListenerFailureBehavior
listener-failure-behavior
ApplicationProtocolConfig$SelectedListenerFailureBehavior/ACCEPT
{:keys [key-manager-factory]} (keystore-fn)
protocol-config
(ApplicationProtocolConfig.
ApplicationProtocolConfig$Protocol/ALPN
selector-failure-behavior
listener-failure-behavior
[ApplicationProtocolNames/HTTP_2
ApplicationProtocolNames/HTTP_1_1])
ssl-context
(-> (SslContextBuilder/forServer key-manager-factory)
(.applicationProtocolConfig protocol-config)
(.build))]
{:http (http/start-server handler {:port port})
:https
(http/start-server
handler
{:port ssl-port
:http-versions [:http2 :http1]
:ssl-context ssl-context})}))
:stop (stop-server))
(defn stop-server []
(when http-server (.stop http-server)))
(let [{:keys [http https]} http-server]
(when (and http https)
(.close http)
(.close https))))
(mount/defstate ^{:on-reload :noop} http-server-context
:start
(when (env :server-enabled?)
(log/infof "You can connect to %s externally" (external-connect-url))
(log/infof "You can connect to %s" (external-connect-url))))
(log/infof "You can connect to %s for internal configuration"
(internal-server-url))))
(defn- restart-web []
(log/info "Restarting web listener")
(mount/stop
#'mylife.core/ssl-options
#'mylife.core/keystore-fn
#'mylife.core/http-server)
(mount/start
#'mylife.core/ssl-options
#'mylife.core/keystore-fn
#'mylife.core/http-server)
(log/info "Web listener restart complete"))
(defn- check-acme-ssl []
(let [{:keys [keystore key-password-fn]} (ssl-options)]
(let [{:keys [keystore-path key-password-fn]} (keystore-fn)]
(try
(ssl/guarantee-acme-cert!
(merge
(select-keys (env) [:domain :cert-dir :acme-server-uri])
{:new-cert-fn
(fn [private-key cert-chain]
(log/infof
(str
"Saving new LetsEncrypt certificate chain to '%s'. It may "
"take up to a minute for the new cert to be available")
keystore)
keystore-path)
(ssl/save-cert-chain!
keystore
keystore-path
private-key
cert-chain
(key-password-fn))
(restart-web))}))
(catch Throwable t
(log/error t "Unable to generate SSL cert")))))
|
︙ | | |
Changes to src/mylife/ssl.clj.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
+
+
|
(ns mylife.ssl
(:require [clojure.java.io :as io]
[mylife.disk-safety :as s]
[taoensso.timbre :as log])
(:import (java.io File FileWriter FileReader FileInputStream)
(java.security KeyPairGenerator Security SecureRandom KeyStore
PrivateKey KeyPair)
(java.security.cert X509Certificate CertificateFactory)
(java.time Instant)
(java.time.temporal ChronoUnit)
(java.util Date Collection)
(javax.net.ssl KeyManagerFactory)
(org.bouncycastle.asn1.x500 X500Name)
(org.bouncycastle.operator.jcajce JcaContentSignerBuilder)
(org.bouncycastle.jce.provider BouncyCastleProvider)
(org.bouncycastle.cert.jcajce JcaX509v3CertificateBuilder
JcaX509CertificateConverter)
(org.shredzone.acme4j.util KeyPairUtils CSRBuilder)
(org.shredzone.acme4j Session
AccountBuilder
Order
Status
Authorization)
(org.shredzone.acme4j.challenge Http01Challenge)
(org.shredzone.acme4j.exception AcmeRetryAfterException)))
(Security/addProvider (BouncyCastleProvider.))
(def ^:private acme-token (atom nil))
(def ^:private acme-content (atom nil))
(def ^:private keystore-type "JKS")
;; "Borrowed" from
(comment
(str "https://github.com/neo4j/neo4j/blob/3.5/community/ssl/src/main/"
"java/org/neo4j/ssl/PkiUtils.java#L94"))
(defn self-signed-cert [domain]
|
︙ | | |
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
|
(defn acme-cert-route []
["/.well-known/acme-challenge/:token"
{:get {:handler get-acme-content}}])
(defn validate-keystore [^File keystore-file
^String password]
(with-open [is (FileInputStream. keystore-file)]
(doto (KeyStore/getInstance "JKS") (.load is (.toCharArray password)))))
(doto (KeyStore/getInstance keystore-type)
(.load is (.toCharArray password)))))
(defn save-cert-chain! [^String keystore-path
^PrivateKey private-key
^Collection certs
^String password]
(let [key-store (doto (KeyStore/getInstance "JKS") (.load nil nil))
(let [key-store (doto (KeyStore/getInstance keystore-type) (.load nil nil))
password (.toCharArray password)]
(.setKeyEntry key-store
"certificate"
private-key
password
(into-array X509Certificate certs))
(with-open [out (io/output-stream keystore-path)]
(.store key-store out password))))
(defn save-single-cert! [^String keystore-path
^PrivateKey private-key
^X509Certificate cert
^String password]
(save-cert-chain! keystore-path private-key [cert] password))
(defn load-key-manager-factory [keystore-path keystore-password]
(with-open [keystore-is (io/input-stream keystore-path)]
(let [keystore-password (.toCharArray keystore-password)
keystore
(doto (KeyStore/getInstance keystore-type)
(.load keystore-is keystore-password))]
(doto (KeyManagerFactory/getInstance "SunX509")
(.init keystore keystore-password)))))
|