├── .github
├── config
│ └── logback.xml
├── renovate.json
├── scripts
│ └── test.sh
└── workflows
│ ├── develop.yml
│ ├── release.yml
│ └── test_install_script.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── NOTICE
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── install.sh
├── scripting-definition
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── ScriptDefinition.kt
│ ├── annotation
│ └── Import.kt
│ └── script
│ └── ServerScript.kt
├── scripting-host
├── build.gradle.kts
└── src
│ ├── main
│ ├── kotlin
│ │ ├── CacheImpl.kt
│ │ ├── FCGIRecord.kt
│ │ ├── FCGIRequestMessage.kt
│ │ ├── FCGIResponseMessage.kt
│ │ ├── KeyValuePair.kt
│ │ ├── Main.kt
│ │ ├── ManagementRequestState.kt
│ │ ├── RequestState.kt
│ │ ├── ScriptingHost.kt
│ │ └── okio.kt
│ └── resources
│ │ ├── kss.properties
│ │ └── logback.xml
│ ├── release
│ ├── config
│ │ ├── kss.properties.sample
│ │ └── logback.xml.sample
│ └── service
│ │ ├── kss.plist
│ │ └── kss.service
│ └── test
│ ├── kotlin
│ ├── ServerScriptingHostCacheTest.kt
│ ├── ServerScriptingHostScriptsTest.kt
│ └── TestUtils.kt
│ └── resources
│ ├── big_output.body
│ ├── big_output.server.kts
│ ├── cache_data.body
│ ├── cache_data.server.kts
│ ├── cache_import_script_1.body
│ ├── cache_import_script_1.server.kts
│ ├── cache_import_script_2.body
│ ├── cache_import_script_2.server.kts
│ ├── custom_headers.body
│ ├── custom_headers.header
│ ├── custom_headers.server.kts
│ ├── dependency.body
│ ├── dependency.server.kts
│ ├── dependency_repository.body
│ ├── dependency_repository.server.kts
│ ├── empty.server.kts
│ ├── exception.header
│ ├── exception.server.kts
│ ├── exception.status
│ ├── import_function.body
│ ├── import_function.server.kts
│ ├── import_invalid.header
│ ├── import_invalid.server.kts
│ ├── import_invalid.status
│ ├── import_not_existent.header
│ ├── import_not_existent.server.kts
│ ├── import_not_existent.status
│ ├── import_script.body
│ ├── import_script.header
│ ├── import_script.server.kts
│ ├── imports
│ ├── invocation_counter.server.kts
│ ├── no_cache_control.server.kts
│ └── to_json.server.kts
│ ├── invalid.header
│ ├── invalid.server.kts
│ ├── invalid.status
│ ├── known_status.body
│ ├── known_status.header
│ ├── known_status.server.kts
│ ├── multiple_output.body
│ ├── multiple_output.server.kts
│ ├── simple_script.body
│ ├── simple_script.server.kts
│ ├── unknown_status.body
│ ├── unknown_status.header
│ └── unknown_status.server.kts
└── settings.gradle.kts
/.github/config/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | log.txt
26 |
27 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp-%mdc- %msg%n
28 |
29 |
30 |
31 |
32 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp-%mdc- %msg%n
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ],
6 | "kotlin-script": {
7 | "fileMatch": ["^.+\\.server\\.kts$"],
8 | "ignorePaths": []
9 | },
10 | "labels": ["dependencies"]
11 | }
12 |
--------------------------------------------------------------------------------
/.github/scripts/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #
4 | # Copyright 2024 Eduard Wolf
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | set -eu
20 | set -o pipefail
21 |
22 | html_dir="$1"
23 |
24 | cp -R scripting-host/src/test/resources/imports "$html_dir"
25 |
26 | for file in scripting-host/src/test/resources/*.server.kts; do
27 | cp "$file" "$html_dir"
28 | test_file_name=$(basename "$file")
29 | test_name="${test_file_name%%.server.kts}"
30 | expected_body="${file%%.server.kts}.body"
31 | header_file="${file%%.server.kts}.header"
32 | expected_header='expected_header.txt'
33 | if [ -f "$header_file" ]; then
34 | sed 's%Status:%HTTP/1.1 %g' "$header_file" > "$expected_header"
35 | else
36 | echo "HTTP/1.1 200 OK" > "$expected_header"
37 | fi
38 | echo "test $test_name"
39 |
40 | function test_request() {
41 | expected="$1"
42 | curl --request GET -s \
43 | --url "http://localhost:8080/$test_name.kts" \
44 | --output 'test_result.txt' \
45 | --dump-header 'test_headers.txt'
46 | if [ -f "$expected" ]; then
47 | diff "$expected" 'test_result.txt'
48 | elif [ -s 'test_result.txt' ]; then
49 | echo 'expected empty body but got'
50 | cat 'test_result.txt'
51 | fi
52 | if [[ -n "$expected_header" ]]; then
53 | missing_lines=$(diff --ignore-all-space -U 0 expected_header.txt test_headers.txt | tail -n +3 | grep -c '^-' || true)
54 | if [ "$missing_lines" -gt 0 ]; then
55 | echo "expected headers [$(cat expected_header.txt)] but found [$(cat test_headers.txt)]"
56 | exit 1
57 | fi
58 | fi
59 | }
60 | if [ "$test_name" == 'cache_data' ]; then
61 | for (( i = 1; i <= 5; i++ )); do
62 | sed "s/{counter}/$i/g" "$expected_body" > 'expected_result.txt'
63 | test_request 'expected_result.txt'
64 | done
65 | else
66 | test_request "$expected_body"
67 | fi
68 | done
69 |
--------------------------------------------------------------------------------
/.github/workflows/develop.yml:
--------------------------------------------------------------------------------
1 | name: Develop
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | push:
7 | branches:
8 | - main
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-24.04
13 | steps:
14 | - name: checkout
15 | uses: actions/checkout@v4.2.2
16 |
17 | - name: Setup JDK
18 | uses: actions/setup-java@v4.6.0
19 | with:
20 | distribution: 'temurin'
21 | java-version: 17
22 |
23 | - name: Setup Gradle
24 | uses: gradle/actions/setup-gradle@v4.2.2
25 |
26 | - name: Build
27 | run: |
28 | ./gradlew build
29 | mv scripting-host/build/distributions/scripting-host-release*.tar.gz scripting-host-release.tar.gz
30 |
31 | - name: Upload build
32 | uses: actions/upload-artifact@v4.6.0
33 | with:
34 | name: release-artifact
35 | path: scripting-host-release.tar.gz
36 | compression-level: '0'
37 | retention-days: 7
38 |
39 | test:
40 | runs-on: ubuntu-24.04
41 | needs: [build]
42 | steps:
43 | - name: checkout
44 | uses: actions/checkout@v4.2.2
45 |
46 | - name: Setup JDK
47 | uses: actions/setup-java@v4.6.0
48 | with:
49 | distribution: 'temurin'
50 | java-version: 17
51 |
52 | - name: Download build
53 | uses: actions/download-artifact@v4.1.8
54 | with:
55 | name: release-artifact
56 |
57 | - name: Untar release artifact
58 | run: |
59 | mkdir scripting-host-release
60 | tar --extract --gunzip --directory scripting-host-release --strip-components 1 --file scripting-host-release.tar.gz
61 |
62 | - name: Test version output
63 | timeout-minutes: 1
64 | run: |
65 | version="$(cat gradle.properties | sed -n 's/version=//p')"
66 | hostVersion=$(./scripting-host-release/bin/kss --version | grep "Version: ")
67 | if [ "$hostVersion" != "Version: $version" ]; then
68 | echo "host version ($hostVersion) doesn't match gradle properties version ($version)"
69 | exit 1
70 | fi
71 |
72 | - name: Start nginx
73 | uses: nyurik/action-setup-nginx@v1.1
74 | id: start_ngingx
75 | with:
76 | conf-file-text: |
77 | user runner docker;
78 | worker_processes 1;
79 | events {
80 | worker_connections 1024;
81 | }
82 | http {
83 | include mime.types;
84 | default_type application/octet-stream;
85 | sendfile on;
86 | keepalive_timeout 65;
87 | server {
88 | listen 8080;
89 | server_name localhost;
90 | location ~ ^(.*)\.kts(\?.*)?$ {
91 | root html;
92 | try_files \$1.server.kts =404;
93 | include fastcgi_params;
94 | fastcgi_pass unix:$RUNNER_TEMP/kss.sock;
95 | }
96 | }
97 | }
98 |
99 | - name: Create test config
100 | run: |
101 | echo "socket.address=$RUNNER_TEMP/kss.sock" >> kss.properties
102 | cp .github/config/logback.xml kss.logback.xml
103 | echo "logging.logback.configurationFile=kss.logback.xml" >> kss.properties
104 |
105 | - name: Run tests on nginx
106 | uses: BerniWittmann/background-server-action@v1.1.1
107 | with:
108 | start: ./scripting-host-release/bin/kss
109 | wait-on: sleep 5
110 | command: ./.github/scripts/test.sh "${{ steps.start_ngingx.outputs.html-dir }}"
111 |
112 | - name: Test logfile not empty
113 | run: |
114 | if [ ! -f log.txt ]; then
115 | echo "log.txt file doesn't exist"
116 | exit 1
117 | fi
118 | if [ ! -s log.txt ]; then
119 | echo 'log.txt file is empty'
120 | exit 2
121 | fi
122 |
123 | - name: Print nginx error logs
124 | if: ${{ failure() }}
125 | run: cat "${{ steps.start_ngingx.outputs.error-log }}"
126 |
127 | - name: Print nginx access logs
128 | if: ${{ failure() }}
129 | run: cat "${{ steps.start_ngingx.outputs.access-log }}"
130 |
131 | - name: Print kss process logs
132 | if: ${{ failure() }}
133 | run: cat log.txt
134 |
135 | test_service:
136 | strategy:
137 | fail-fast: false
138 | matrix:
139 | runner:
140 | - ubuntu-24.04
141 | - macos-14
142 | runs-on: ${{ matrix.runner }}
143 | needs: build
144 | steps:
145 | - name: checkout
146 | uses: actions/checkout@v4.2.2
147 |
148 | - name: Setup JDK
149 | uses: actions/setup-java@v4.6.0
150 | with:
151 | distribution: 'temurin'
152 | java-version: 17
153 |
154 | # HOME directory needs to be accessible by _www, so the java executable can be used
155 | # not needed for linux, because JDK is not put in user directory
156 | - name: Set launchd path
157 | if: ${{ startsWith(matrix.runner, 'macos') }}
158 | run: |
159 | chmod o+rx "$HOME"
160 | sudo launchctl setenv JAVA_HOME "$JAVA_HOME"
161 |
162 | - name: Download build
163 | uses: actions/download-artifact@v4.1.8
164 | with:
165 | name: release-artifact
166 |
167 | - name: install latest version
168 | run: sudo ./install.sh --release-fetch-mode archive=scripting-host-release.tar.gz
169 |
170 | - name: setup runner config
171 | id: runner_config
172 | run: |
173 | if [[ '${{ matrix.runner }}' == ubuntu* ]]
174 | then
175 | echo 'config_dir=/usr/share/kss/' >> $GITHUB_OUTPUT
176 | echo 'start_cmd=systemctl start kss' >> $GITHUB_OUTPUT
177 | echo 'user=www-data' >> $GITHUB_OUTPUT
178 | else
179 | echo 'config_dir=/Library/Application Support/kss/' >> $GITHUB_OUTPUT
180 | echo 'start_cmd=launchctl load -w /Library/LaunchDaemons/kss.plist' >> $GITHUB_OUTPUT
181 | echo 'user=_www' >> $GITHUB_OUTPUT
182 | fi
183 |
184 | - name: setup socket
185 | run: |
186 | sudo mkdir -p /var/run/kss/
187 | sudo chown '${{ steps.runner_config.outputs.user }}' /var/run/kss/
188 |
189 | - name: Start kss
190 | run: |
191 | sudo cp .github/config/logback.xml '${{ steps.runner_config.outputs.config_dir }}kss.logback.xml'
192 | echo "logging.logback.configurationFile=${{ steps.runner_config.outputs.config_dir }}kss.logback.xml" >> kss.properties
193 | echo "dependencies.maven.homeDirectory=${{ steps.runner_config.outputs.config_dir }}.m2" >> kss.properties
194 | sudo chown '${{ steps.runner_config.outputs.user }}' '${{ steps.runner_config.outputs.config_dir }}'
195 | sudo mkdir '${{ steps.runner_config.outputs.config_dir }}.m2'
196 | sudo chown '${{ steps.runner_config.outputs.user }}' '${{ steps.runner_config.outputs.config_dir }}.m2'
197 | sudo mv kss.properties '${{ steps.runner_config.outputs.config_dir }}'
198 | sudo ${{ steps.runner_config.outputs.start_cmd }}
199 | sleep 2
200 |
201 | - name: Start nginx
202 | uses: nyurik/action-setup-nginx@v1.1
203 | id: start_ngingx
204 | with:
205 | conf-file-text: |
206 | user ${{ steps.runner_config.outputs.user }};
207 | worker_processes 1;
208 | events {
209 | worker_connections 1024;
210 | }
211 | http {
212 | include mime.types;
213 | default_type application/octet-stream;
214 | sendfile on;
215 | keepalive_timeout 65;
216 | server {
217 | listen 8080;
218 | server_name localhost;
219 | location ~ ^(.*)\.kts(\?.*)?$ {
220 | root html;
221 | try_files \$1.server.kts =404;
222 | include fastcgi_params;
223 | fastcgi_pass unix:/var/run/kss/kss.sock;
224 | }
225 | }
226 | }
227 |
228 | - name: run tests
229 | run: ./.github/scripts/test.sh "${{ steps.start_ngingx.outputs.html-dir }}"
230 |
231 | - name: Print nginx error logs
232 | if: ${{ failure() }}
233 | run: cat "${{ steps.start_ngingx.outputs.error-log }}"
234 |
235 | - name: Print nginx access logs
236 | if: ${{ failure() }}
237 | run: cat "${{ steps.start_ngingx.outputs.access-log }}"
238 |
239 | - name: Print kss process logs
240 | if: ${{ failure() }}
241 | run: cat '${{ steps.runner_config.outputs.config_dir }}log.txt'
242 |
243 | - name: Print macos kss process logs
244 | if: ${{ failure() && startsWith(matrix.runner, 'macos') }}
245 | run: |
246 | if [ -f '/Library/Logs/kss.log' ]
247 | then
248 | cat '/Library/Logs/kss.log'
249 | fi
250 |
251 | - name: Print macos kss error logs
252 | if: ${{ failure() && startsWith(matrix.runner, 'macos') }}
253 | run: |
254 | if [ -f '/Library/Logs/kss-error.log' ]
255 | then
256 | cat '/Library/Logs/kss-error.log'
257 | fi
258 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | createRelease:
10 | runs-on: ubuntu-24.04
11 | permissions:
12 | contents: write
13 | steps:
14 | - name: checkout
15 | uses: actions/checkout@v4.2.2
16 |
17 | - name: set up JDK
18 | uses: actions/setup-java@v4.6.0
19 | with:
20 | distribution: 'temurin'
21 | java-version: 17
22 |
23 | - name: Build
24 | run: ./gradlew assembleReleaseDist
25 |
26 | - name: Create info variables
27 | id: variables
28 | run: |
29 | gradleVersion="$(cat gradle.properties | sed -n 's/version=//p')"
30 | gitVersion="${GITHUB_REF#refs/*/}"
31 | if [ "$gradleVersion" != "$gitVersion" ]; then
32 | echo "gradle properties version ($gradleVersion) doesn't match git tag version ($gitVersion)"
33 | exit 1
34 | fi
35 | echo "version=$gitVersion" >> $GITHUB_OUTPUT
36 |
37 | - name: Get Changelog
38 | id: changelog
39 | uses: mindsers/changelog-reader-action@v2.2.3
40 | with:
41 | version: ${{ steps.variables.outputs.version }}
42 | validation_level: error
43 |
44 | - name: Create Release
45 | uses: softprops/action-gh-release@v2
46 | with:
47 | body: "${{ steps.changelog.outputs.changes }}"
48 | fail_on_unmatched_files: true
49 | files: |
50 | scripting-host/build/distributions/scripting-host-release*.tar.gz
51 | scripting-host/build/distributions/scripting-host-release*.zip
52 | draft: false
53 | prerelease: false
54 |
--------------------------------------------------------------------------------
/.github/workflows/test_install_script.yml:
--------------------------------------------------------------------------------
1 | name: Test Install Script
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - 'install.sh'
7 | - '.github/workflows/test_install_script.yml'
8 | release:
9 | types: [released]
10 |
11 | jobs:
12 | test_install_script:
13 | strategy:
14 | matrix:
15 | runner:
16 | - ubuntu-24.04
17 | - macos-14
18 | version:
19 | - '0.3.0'
20 | - '0.4.0'
21 | - '0.5.0'
22 | - '0.6.0'
23 | - ''
24 | exclude:
25 | - runner: macos-14
26 | version: 0.3.0
27 | runs-on: ${{ matrix.runner }}
28 | steps:
29 | - name: checkout
30 | uses: actions/checkout@v4.2.2
31 |
32 | - name: Setup JDK
33 | uses: actions/setup-java@v4.6.0
34 | with:
35 | distribution: 'temurin'
36 | java-version: 17
37 |
38 | # HOME directory needs to be accessible by _www, so the java executable can be used
39 | # not needed for linux, because JDK is not put in user directory
40 | - name: Set launchd path
41 | if: ${{ startsWith(matrix.runner, 'macos') }}
42 | run: |
43 | chmod o+rx "$HOME"
44 | sudo launchctl setenv JAVA_HOME "$JAVA_HOME"
45 |
46 | - name: install latest version
47 | if: ${{ matrix.version == '' }}
48 | run: sudo --preserve-env ./install.sh
49 | env:
50 | GH_TOKEN: ${{ github.token }}
51 |
52 | - name: install specific version
53 | if: ${{ matrix.version != '' }}
54 | run: sudo --preserve-env ./install.sh --release-version ${{ matrix.version }}
55 | env:
56 | GH_TOKEN: ${{ github.token }}
57 |
58 | - name: setup socket
59 | run: |
60 | sudo mkdir -p /var/run/kss/
61 | sudo chmod a+w /var/run/kss/
62 |
63 | - name: add timeout command to osx
64 | if: ${{ startsWith(matrix.runner, 'macos') }}
65 | run: |
66 | brew install coreutils
67 | sudo ln -s /usr/local/bin/gtimeout /usr/local/bin/timeout
68 |
69 | - name: check install
70 | # 124 is cancelled after timeout
71 | run: |
72 | timeout 5s kss || exit_code=$?
73 | if [ "$exit_code" -ne 124 ]
74 | then
75 | echo "expected exit code 124 but was $exit_code"
76 | exit "$exit_code"
77 | fi
78 |
79 | - name: setup runner config
80 | id: runner_config
81 | run: |
82 | if [[ '${{ matrix.runner }}' == ubuntu* ]]
83 | then
84 | echo 'config_dir=/usr/share/kss/' >> $GITHUB_OUTPUT
85 | echo 'start_cmd=systemctl start kss' >> $GITHUB_OUTPUT
86 | echo 'user=www-data' >> $GITHUB_OUTPUT
87 | else
88 | echo 'config_dir=/Library/Application Support/kss/' >> $GITHUB_OUTPUT
89 | echo 'start_cmd=launchctl load -w /Library/LaunchDaemons/kss.plist' >> $GITHUB_OUTPUT
90 | echo 'user=_www' >> $GITHUB_OUTPUT
91 | fi
92 |
93 | - name: setup socket
94 | run: |
95 | sudo mkdir -p /var/run/kss/
96 | sudo chown '${{ steps.runner_config.outputs.user }}' /var/run/kss/
97 |
98 | - name: Start kss
99 | run: |
100 | sudo cp .github/config/logback.xml '${{ steps.runner_config.outputs.config_dir }}kss.logback.xml'
101 | echo "logging.logback.configurationFile=${{ steps.runner_config.outputs.config_dir }}kss.logback.xml" >> kss.properties
102 | echo "dependencies.maven.homeDirectory=${{ steps.runner_config.outputs.config_dir }}.m2" >> kss.properties
103 | sudo chown '${{ steps.runner_config.outputs.user }}' '${{ steps.runner_config.outputs.config_dir }}'
104 | sudo mkdir '${{ steps.runner_config.outputs.config_dir }}.m2'
105 | sudo chown '${{ steps.runner_config.outputs.user }}' '${{ steps.runner_config.outputs.config_dir }}.m2'
106 | sudo mv kss.properties '${{ steps.runner_config.outputs.config_dir }}'
107 | sudo ${{ steps.runner_config.outputs.start_cmd }}
108 | sleep 5
109 |
110 | - name: test linux service exists
111 | if: ${{ startsWith(matrix.runner, 'ubuntu') }}
112 | run: systemctl status kss.service
113 |
114 | - name: test macos service exists
115 | if: ${{ startsWith(matrix.runner, 'macos') }}
116 | run: sudo launchctl list 'net.edwardday.kss'
117 |
118 | - name: upgrade to latest version
119 | run: |
120 | sudo --preserve-env ./install.sh
121 | sleep 5
122 | env:
123 | GH_TOKEN: ${{ github.token }}
124 |
125 | - name: test linux service exists after upgrade
126 | if: ${{ startsWith(matrix.runner, 'ubuntu') }}
127 | run: systemctl status kss.service
128 |
129 | - name: test macos service exists after upgrade
130 | if: ${{ startsWith(matrix.runner, 'macos') }}
131 | run: sudo launchctl list 'net.edwardday.kss'
132 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/
9 |
10 | ### Eclipse ###
11 | .apt_generated
12 | .classpath
13 | .factorypath
14 | .project
15 | .settings
16 | .springBeans
17 | .sts4-cache
18 | bin/
19 | !**/src/main/**/bin/
20 | !**/src/test/**/bin/
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 |
29 | ### VS Code ###
30 | .vscode/
31 |
32 | ### Mac OS ###
33 | .DS_Store
34 |
35 | ### Project ###
36 | /kss.properties
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## [UNRELEASED]
4 |
5 | ## [0.6.0] - 2025-01-25
6 |
7 | ### Added
8 |
9 | - script exception handling
10 | - add option to import other scripts via `@Import` annotation
11 | - upgrade current service via install script
12 |
13 | ## [0.5.0] - 2025-01-05
14 |
15 | ### Added
16 |
17 | - test for `@Repository` annotation
18 | - change logging configuration via properties file
19 | - test for install script
20 | - option to install via release archive file
21 | - option to set local maven repository settings
22 | - version and help command line option
23 | - option to specify install version explicitly
24 |
25 | ### Changed
26 |
27 | - updated various dependencies
28 | - default user for macos set to _www
29 |
30 | ## [0.4.0] - 2024-08-25
31 |
32 | ### Added
33 |
34 | - MacOS option in install script
35 |
36 | ## [0.3.0] - 2024-08-18
37 |
38 | ### Added
39 |
40 | - Install script
41 | - sample configuration file
42 | - README
43 |
44 | ### Changed
45 |
46 | - add working directory to service file
47 |
48 | ## [0.2.0] - 2024-08-17
49 |
50 | ### Added
51 |
52 | - Service file in release
53 |
54 | ### Changed
55 |
56 | - Release tar archive gzip
57 | - Adapt release content structure
58 |
59 | ## [0.1.0] - 2024-07-06
60 |
61 | ### Added
62 |
63 | - Initial Version
64 |
65 | [0.5.0]: https://github.com/EdwarDDay/kotlin-server-scripts/releases/tag/0.5.0
66 |
67 | [0.4.0]: https://github.com/EdwarDDay/kotlin-server-scripts/releases/tag/0.4.0
68 |
69 | [0.3.0]: https://github.com/EdwarDDay/kotlin-server-scripts/releases/tag/0.3.0
70 |
71 | [0.2.0]: https://github.com/EdwarDDay/kotlin-server-scripts/releases/tag/0.2.0
72 |
73 | [0.1.0]: https://github.com/EdwarDDay/kotlin-server-scripts/releases/tag/0.1.0
74 |
75 | [UNRELEASED]: https://github.com/EdwarDDay/kotlin-server-scripts
76 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright 2024 Eduard Wolf
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kotlin Server Scripts
2 |
3 | Kotlin Server Scripts let you run kotlin scripts in response to server requests, without setting up a complete project.
4 |
5 | ## Setup
6 |
7 | There is an easy to use install script to install the latest release on a linux system. It downloads the release
8 | archive, installs the executable and creates and enables a system service.
9 |
10 | ```shell
11 | curl 'https://raw.githubusercontent.com/EdwarDDay/kotlin-server-scripts/main/install.sh' --output install.sh
12 | chmod u+x install.sh
13 | ./install.sh # admin privileges might be needed
14 | ```
15 |
16 | The `install.sh` script will put the executable in a directory, creates a system service file with a service execution
17 | user and puts a configuration sample file in a config directory based on the OS. If you want to configure any of these
18 | settings or see the default values, execute `./install.sh --help` for more information.
19 |
20 | To fetch the release asset, the script needs either authorized access to the GitHub API - either via a GH PAT
21 | (`--token` option) or via the authenticated `gh` commandline tool - or needs the `jq` tool to parse the GitHub REST
22 | response.
23 |
24 | ### Nginx setup
25 |
26 | To execute server scripts, add something like the following configuration to you nginx.conf:
27 |
28 | ```nginx
29 | # match all kts files
30 | location ~ ^(.*)\.kts(\?.*)?$ {
31 | # match the server script with .server.kts extension
32 | try_files \$1.server.kts =404;
33 | # use default fastcgir parameters
34 | include fastcgi_params;
35 | # pass request to kss process
36 | fastcgi_pass unix:/var/run/kss/kss.sock;
37 | }
38 | ```
39 |
40 | ## Example
41 |
42 | Example script looks like:
43 |
44 | `HelloWorld.server.kts`
45 | ```kotlin
46 | setHeader("Content-Type", "text/html")
47 | status(200)
48 | writeOutput(
49 | """
50 |
51 |
52 |
53 | Hello Kotlin
54 |
55 |
56 | Hello Kotlin
57 |
59 | """
60 | )
61 | ```
62 |
63 | You can find more examples in the [test folder](scripting-host/src/test/resources)
64 |
65 | ### Add dependencies
66 |
67 | If you want to add a maven dependency, you can add a `@file:DependsOn(""")` annotation to the file. If your
68 | dependency isn't in the maven central repository, you can add additional repositories via a
69 | `@file:Repository("")` annotation at file level.
70 |
71 | ### Add script dependencies
72 |
73 | If you want to execute another `server.kts` script file, you can import it via `@file:Import("")`.
74 |
75 | ### Change logging configuration
76 |
77 | You can reference a [logback configuration file](https://logback.qos.ch/manual/configuration.html) in the
78 | `kss.properties` configuration file via `logging.logback.configurationFile=`. This way you can change the
79 | log level from the default `info` or log to a file instead of the command line as it's done by default.
80 |
81 | ## Building
82 |
83 | To build the library just run `./gradlew scripting-host:build`.
84 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Eduard Wolf
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | alias(libs.plugins.kotlin.jvm) apply false
19 | }
20 |
21 | group = "net.edwardday.serverscript"
22 |
23 | allprojects {
24 | repositories {
25 | mavenCentral()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2024 Eduard Wolf
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | kotlin.code.style=official
18 | org.gradle.caching=true
19 | org.gradle.parallel=true
20 |
21 | version=0.7.0-SNAPSHOT
22 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | clikt = "5.0.2"
3 | kotlin = "2.1.0"
4 | kotlinx-coroutines = "1.10.1"
5 | logback = "1.5.16"
6 | oshai-logging = "7.0.3"
7 | okio = "3.10.2"
8 |
9 | [libraries]
10 | clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
11 | kotlin-scripting-common = { module = "org.jetbrains.kotlin:kotlin-scripting-common", version.ref = "kotlin" }
12 | kotlin-scripting-dependencies = { module = "org.jetbrains.kotlin:kotlin-scripting-dependencies", version.ref = "kotlin" }
13 | kotlin-scripting-dependencies-maven = { module = "org.jetbrains.kotlin:kotlin-scripting-dependencies-maven", version.ref = "kotlin" }
14 | kotlin-scripting-jvm = { module = "org.jetbrains.kotlin:kotlin-scripting-jvm", version.ref = "kotlin" }
15 | kotlin-scripting-jvm-host = { module = "org.jetbrains.kotlin:kotlin-scripting-jvm-host", version.ref = "kotlin" }
16 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
17 | kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
18 | kotlinx-coroutines-slf4j = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-slf4j", version.ref = "kotlinx-coroutines" }
19 | kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
20 | logback-logger = { module = "ch.qos.logback:logback-classic", version.ref = "logback"}
21 | oshai-logging = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "oshai-logging" }
22 | okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
23 |
24 | [plugins]
25 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
26 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EdwarDDay/kotlin-server-scripts/cdfed81c9ee3780333eb9e6feae8d10940dc1b84/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | if ! command -v java >/dev/null 2>&1
137 | then
138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
139 |
140 | Please set the JAVA_HOME variable in your environment to match the
141 | location of your Java installation."
142 | fi
143 | fi
144 |
145 | # Increase the maximum file descriptors if we can.
146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
147 | case $MAX_FD in #(
148 | max*)
149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
150 | # shellcheck disable=SC2039,SC3045
151 | MAX_FD=$( ulimit -H -n ) ||
152 | warn "Could not query maximum file descriptor limit"
153 | esac
154 | case $MAX_FD in #(
155 | '' | soft) :;; #(
156 | *)
157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
158 | # shellcheck disable=SC2039,SC3045
159 | ulimit -n "$MAX_FD" ||
160 | warn "Could not set maximum file descriptor limit to $MAX_FD"
161 | esac
162 | fi
163 |
164 | # Collect all arguments for the java command, stacking in reverse order:
165 | # * args from the command line
166 | # * the main class name
167 | # * -classpath
168 | # * -D...appname settings
169 | # * --module-path (only if needed)
170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
171 |
172 | # For Cygwin or MSYS, switch paths to Windows format before running java
173 | if "$cygwin" || "$msys" ; then
174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
176 |
177 | JAVACMD=$( cygpath --unix "$JAVACMD" )
178 |
179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
180 | for arg do
181 | if
182 | case $arg in #(
183 | -*) false ;; # don't mess with options #(
184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
185 | [ -e "$t" ] ;; #(
186 | *) false ;;
187 | esac
188 | then
189 | arg=$( cygpath --path --ignore --mixed "$arg" )
190 | fi
191 | # Roll the args list around exactly as many times as the number of
192 | # args, so each arg winds up back in the position where it started, but
193 | # possibly modified.
194 | #
195 | # NB: a `for` loop captures its iteration list before it begins, so
196 | # changing the positional parameters here affects neither the number of
197 | # iterations, nor the values presented in `arg`.
198 | shift # remove old arg
199 | set -- "$@" "$arg" # push replacement arg
200 | done
201 | fi
202 |
203 |
204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
206 |
207 | # Collect all arguments for the java command:
208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
209 | # and any embedded shellness will be escaped.
210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
211 | # treated as '${Hostname}' itself on the command line.
212 |
213 | set -- \
214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
215 | -classpath "$CLASSPATH" \
216 | org.gradle.wrapper.GradleWrapperMain \
217 | "$@"
218 |
219 | # Stop when "xargs" is not available.
220 | if ! command -v xargs >/dev/null 2>&1
221 | then
222 | die "xargs is not available"
223 | fi
224 |
225 | # Use "xargs" to parse quoted args.
226 | #
227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
228 | #
229 | # In Bash we could simply go:
230 | #
231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
232 | # set -- "${ARGS[@]}" "$@"
233 | #
234 | # but POSIX shell has neither arrays nor command substitution, so instead we
235 | # post-process each arg (as a line of input to sed) to backslash-escape any
236 | # character that might be a shell metacharacter, then use eval to reverse
237 | # that process (while maintaining the separation between arguments), and wrap
238 | # the whole thing up as a single "set" statement.
239 | #
240 | # This will of course break if any of these variables contains a newline or
241 | # an unmatched quote.
242 | #
243 |
244 | eval "set -- $(
245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
246 | xargs -n1 |
247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
248 | tr '\n' ' '
249 | )" '"$@"'
250 |
251 | exec "$JAVACMD" "$@"
252 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #
4 | # Copyright 2024 Eduard Wolf
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | set -eu
20 | set -o pipefail
21 |
22 | if [[ -n $(command -v systemctl || echo '') ]]; then
23 | echo 'use systemctl default values' >&2
24 | service_binary='systemctl'
25 | execution_directory='/usr/bin/'
26 | service_directory='/etc/systemd/system/'
27 | configuration_directory='/usr/share/kss/'
28 | log_directory=''
29 | service_file='kss.service'
30 | service_user='www-data'
31 | elif [[ -n $(command -v launchctl || echo '') ]]; then
32 | echo 'use launchctl default values' >&2
33 | service_binary='launchctl'
34 | execution_directory='/usr/local/bin/'
35 | service_directory='/Library/LaunchDaemons/'
36 | configuration_directory='/Library/Application Support/kss/'
37 | log_directory='/Library/Logs/'
38 | service_file='kss.plist'
39 | service_user='_www'
40 | else
41 | echo 'use empty default values' >&2
42 | service_binary=''
43 | execution_directory='/usr/bin/'
44 | service_directory=''
45 | configuration_directory=''
46 | log_directory=''
47 | service_file='kss.service'
48 | service_user=''
49 | fi
50 |
51 | if [[ -n $(command -v gh || echo '') ]]; then
52 | release_fetch_mode="gh"
53 | else
54 | release_fetch_mode="unknown"
55 | fi
56 |
57 | PROGRAM_NAME=$(basename "$0")
58 |
59 | function usageText {
60 | echo 'Usage: '"$PROGRAM_NAME"' [OPTIONS]'
61 | echo 'install latest kss release'
62 | echo ''
63 | echo 'Options:'
64 | echo '-h/--help prints this usage'
65 | echo '-t/--token github token to use to download executables'
66 | echo "-d/--directory[$execution_directory]"
67 | echo ' directory for binary files'
68 | echo '-r/--release-fetch-mode Options:'
69 | echo ' gh - use authenticated Github commandline tool'
70 | echo ' curl-authenticated - use curl authenticated with the token from the --token option'
71 | echo ' curl - use curl without token - needs jq to parse rest response'
72 | echo ' archive= - use tar archive as release'
73 | echo '-v/--release-version Specify explicit release version (uses latest version by default)'
74 | echo "-s/--service-directory[$service_directory]"
75 | echo ' directory for service files'
76 | echo "-c/--configuration-directory[$configuration_directory]"
77 | echo ' directory for configuration files'
78 | echo "-l/--log-directory[$log_directory]"
79 | echo ' directory for log files (only used in MacOS)'
80 | echo "-u/--user[$service_user] user which runs the service process"
81 | }
82 |
83 | function usage {
84 | if [ -z "${1+''}" ]
85 | then
86 | usageText
87 | exit 0
88 | else
89 | echo "$1" >&2
90 | usageText >&2
91 | exit 1
92 | fi
93 | }
94 |
95 | authorization_token=''
96 | release_version=''
97 | release_fetch_mode_set='false'
98 |
99 | while [[ $# -gt 0 ]]; do
100 | key="$1"
101 | if [ -z "${2+''}" ]; then
102 | value=''
103 | else
104 | value="$2"
105 | fi
106 | case $key in
107 | -h|--help)
108 | usage
109 | ;;
110 | -t|--token)
111 | if [ "$value" ]; then
112 | authorization_token="$value"
113 | if [ "${release_fetch_mode_set}" == 'false' ] && [ "${release_fetch_mode}" != 'gh' ]; then
114 | release_fetch_mode='curl-authenticated'
115 | fi
116 | shift
117 | else
118 | usage 'the --token option needs an argument'
119 | fi
120 | ;;
121 | -d|--directory)
122 | if [ "$value" ]; then
123 | execution_directory="${value%/}/"
124 | shift
125 | else
126 | usage 'the --directory option needs an argument'
127 | fi
128 | ;;
129 | -v|--release-version)
130 | if [ "$value" ]; then
131 | release_version="$value"
132 | shift
133 | else
134 | usage 'the --release-version option needs an argument'
135 | fi
136 | ;;
137 | -r|--release-fetch-mode)
138 | release_fetch_mode_set='true'
139 | case $value in
140 | gh)
141 | if [[ -n $(command -v gh || echo '') ]]; then
142 | release_fetch_mode="gh"
143 | else
144 | usage '--release-fetch-mode set to gh but can'\''t find gh command'
145 | fi
146 | ;;
147 | curl-authenticated)
148 | release_fetch_mode="curl-authenticated"
149 | ;;
150 | curl)
151 | if [[ -n $(command -v jq || echo '') ]]; then
152 | release_fetch_mode="curl"
153 | else
154 | usage '--release-fetch-mode set to curl but can'\''t find jq command, which is needed to parse the REST API'
155 | fi
156 | ;;
157 | archive=*)
158 | release_fetch_mode="archive"
159 | archive_file="${value:8}"
160 | if [ ! -f "$archive_file" ]; then
161 | usage "couldn't find archive file '$archive_file'"
162 | fi
163 | ;;
164 | '')
165 | usage 'the --release-fetch-mode option needs an argument'
166 | ;;
167 | *)
168 | usage "unknown option ${value} for option --release-fetch-mode - please specify gh, curl-authenticated or curl"
169 | ;;
170 | esac
171 | shift
172 | ;;
173 | -s|--service-directory)
174 | if [ "$value" ]; then
175 | service_directory="${value%/}/"
176 | shift
177 | else
178 | usage 'the --service-directory option needs an argument'
179 | fi
180 | ;;
181 | -c|--configuration-directory)
182 | if [ "$value" ]; then
183 | configuration_directory="${value%/}/"
184 | shift
185 | else
186 | usage 'the --configuration-directory option needs an argument'
187 | fi
188 | ;;
189 | -l|--log-directory)
190 | if [ "$value" ]; then
191 | log_directory="${value%/}/"
192 | shift
193 | else
194 | usage 'the --log-directory option needs an argument'
195 | fi
196 | ;;
197 | -u|--user)
198 | if [ "$value" ]; then
199 | service_user="${value}"
200 | shift
201 | else
202 | usage 'the --user option needs an argument'
203 | fi
204 | ;;
205 | *)
206 | usage "unknown option ${key}"
207 | ;;
208 | esac
209 | shift
210 | done
211 |
212 | if [ "${release_fetch_mode}" == 'unknown' ] && [[ -n $(command -v jq || echo '') ]]; then
213 | release_fetch_mode='curl'
214 | fi
215 |
216 | if [ "${release_fetch_mode}" == 'curl-authenticated' ] && [ "${authorization_token}" == '' ]; then
217 | usage "'--release-fetch-mode' with option 'curl-authenticated' needs also '--token' to be specified"
218 | fi
219 |
220 | if [ "${release_fetch_mode}" == 'archive' ] && [ "${release_version}" != '' ]; then
221 | usage "'--release-fetch-mode' with option 'archive' doesn't support a specific release version (--release-version option)"
222 | fi
223 |
224 | if [ "${release_fetch_mode}" == 'unknown' ]; then
225 | usage "please specify '--release-fetch-mode' as no default option was found"
226 | fi
227 |
228 | if [ "${release_fetch_mode}" == 'gh' ]; then
229 | if ! gh auth status >/dev/null && [ -z "${GH_TOKEN:-}" ]; then
230 | usage "gh commandline tool is not setup. Please login via 'gh auth login', specify 'GH_TOKEN' or use '--release-fetch-mode curl' or '--release-fetch-mode curl-authenticated --token '"
231 | fi
232 | fi
233 |
234 | if [ ! -d "${execution_directory}" ]; then
235 | usage "'--directory' is set to '${execution_directory}' which is no existing directory. Please specify an existing directory."
236 | elif [ ! -w "${execution_directory}" ]; then
237 | usage "Current user has no writer permissions for '${execution_directory}'. Please execute with a different user."
238 | fi
239 |
240 | if [ ! -d "${service_directory}" ]; then
241 | echo "'--service-directory' is set to '${service_directory}' which is no existing directory. Service won't be installed" >&2
242 | service_directory=''
243 | elif [ ! -w "${service_directory}" ]; then
244 | echo "Current user has no writer permissions for '${service_directory}'. Service won't be installed" >&2
245 | service_directory=''
246 | elif [ -n "${service_binary}" ]; then
247 | if [ ! -d "${configuration_directory}" ]; then
248 | echo "'--configuration-directory' (${configuration_directory}) not found. Try to create it" >&2
249 | mkdir -p "${configuration_directory}"
250 | fi
251 | if [ -n "${log_directory}" ] && [ ! -d "${log_directory}" ]; then
252 | echo "'--log-directory' (${log_directory}) not found. Try to create it" >&2
253 | mkdir -p "${log_directory}"
254 | fi
255 | if [ ! -w "${configuration_directory}" ]; then
256 | echo "Current user has no writer permissions for '${configuration_directory}'. Service won't be installed" >&2
257 | service_directory=''
258 | elif ! id "$service_user" >/dev/null 2>&1; then
259 | echo "User '$service_user' does not exist. Service won't be installed" >&2
260 | service_directory=''
261 | fi
262 | fi
263 |
264 | authorization_args=()
265 | if [ "$authorization_token" ]; then
266 | authorization_args=(--header "Authorization: Bearer ${authorization_token}")
267 | fi
268 |
269 | github_args=(--location ${authorization_args[@]+"${authorization_args[@]}"} --header 'X-GitHub-Api-Version: 2022-11-28')
270 |
271 | if [ -z "$release_version" ]; then
272 | release_rest_path='latest'
273 | # shellcheck disable=SC2016
274 | query='query ($user: String!, $repo: String!) { repository(owner: $user, name: $repo) { latestRelease { releaseAssets(first: 10) { nodes { contentType url } } } } }'
275 | else
276 | release_rest_path="tags/$release_version"
277 | # shellcheck disable=SC2016
278 | query='query ($user: String!, $repo: String!, $release: String!) { repository(owner: $user, name: $repo) { release(tagName: $release) { releaseAssets(first: 10) { nodes { contentType url } } } } }'
279 | fi
280 |
281 | function extractUrlFromGraphqlQuery() {
282 | local response=$1
283 | if [[ -n $(command -v jq || echo '') ]]; then
284 | if [ -z "$release_version" ]; then
285 | jq --raw-output '.data.repository.latestRelease.releaseAssets.nodes | map(select(.contentType == "application/gzip"))[0].url' <<< "$response"
286 | else
287 | jq --raw-output '.data.repository.release.releaseAssets.nodes | map(select(.contentType == "application/gzip"))[0].url' <<< "$response"
288 | fi
289 | else
290 | local response_tmp="${response#*\"contentType\":\"application\/gzip\",\"url\":\"}"
291 | echo "${response_tmp%%\"*}"
292 | fi
293 | }
294 |
295 | function downloadBinary() {
296 | echo 'download binary' >&2
297 | curl --progress-bar --header 'Accept: application/octet-stream' --url "${url}" --output "${archive_name}" --fail
298 | }
299 |
300 | archive_name='kss.tar.gz'
301 |
302 | case "$release_fetch_mode" in
303 | archive)
304 | echo "use release archive from file $archive_file" >&2
305 | cp "$archive_file" "$archive_name"
306 | ;;
307 | gh)
308 | echo 'download latest release data via gh commandline tool' >&2
309 | response="$(gh api graphql -F 'user=EdwarDDay' -F 'repo=kotlin-server-scripts' -F "release=$release_version" -f "query=$query")"
310 | url="$(extractUrlFromGraphqlQuery "$response")"
311 | downloadBinary
312 | ;;
313 | curl-authenticated)
314 | echo 'download latest release data via curl and graphql api' >&2
315 | data="{
316 | \"query\":\"$query\",
317 | \"variables\":{
318 | \"user\":\"EdwarDDay\",
319 | \"repo\":\"kotlin-server-scripts\",
320 | \"release\":\"$release_version\"
321 | }
322 | }"
323 | response="$(curl --request POST "${github_args[@]}" --fail --silent --url 'https://api.github.com/graphql' --data "$data")"
324 | url="$(extractUrlFromGraphqlQuery "$response")"
325 | downloadBinary
326 | ;;
327 | # curl
328 | *)
329 | echo 'download latest release data via github rest endpoint and jq' >&2
330 | url="$(curl --request GET "${github_args[@]}" --fail --silent --url "https://api.github.com/repos/EdwarDDay/kotlin-server-scripts/releases/$release_rest_path" | jq --raw-output '.assets | map(select(.content_type == "application/gzip"))[0].url')"
331 | downloadBinary
332 | esac
333 |
334 | echo 'extract binary' >&2
335 |
336 | if [ -d "${execution_directory}kss_lib/" ]; then
337 | rm "${execution_directory}kss_lib/"*.jar
338 | fi
339 | archiveInternalName="$(tar --list --file "${archive_name}" --exclude="*/?*")"
340 | tar --extract --gunzip --file "${archive_name}" --strip-components 2 --directory "${execution_directory}" "${archiveInternalName}bin/"
341 |
342 | if [[ -n "${service_directory}" ]]; then
343 | echo 'configure service' >&2
344 | # escape sed escape char
345 | service_user="${service_user/|/\\\|}"
346 | tar --extract --gunzip --file "${archive_name}" --to-stdout "${archiveInternalName}service/$service_file" |\
347 | sed "s|{{DIRECTORY}}|${execution_directory}|g" | \
348 | sed "s|{{WORKING_DIRECTORY}}|${configuration_directory}|g" | \
349 | sed "s|{{LOG_DIRECTORY}}|${log_directory}|g" | \
350 | sed "s|{{USER}}|${service_user}|g" \
351 | > "$service_file"
352 | case $service_binary in
353 | systemctl)
354 | if [ ! -f "$service_directory$service_file" ]; then
355 | mv "$service_file" "$service_directory$service_file"
356 | systemctl enable kss
357 | echo 'The system service is enabled. Run'
358 | echo ''
359 | echo 'systemctl start kss'
360 | echo ''
361 | echo 'to start the service'
362 | elif diff --brief "$service_file" "$service_directory$service_file" > /dev/null; then
363 | rm "$service_file"
364 | echo 'Restart system service'
365 | systemctl restart kss
366 | else
367 | mv "$service_file" "$service_directory$service_file"
368 | echo 'Restart system service'
369 | systemctl daemon-reload
370 | systemctl restart kss
371 | fi
372 | ;;
373 | launchctl)
374 | touch "${log_directory}kss.log"
375 | touch "${log_directory}kss-error.log"
376 | chown "$service_user" "${log_directory}kss.log" "${log_directory}kss-error.log"
377 | if [ ! -f "$service_directory$service_file" ]; then
378 | mv "$service_file" "$service_directory$service_file"
379 | chmod 600 "$service_directory$service_file"
380 | echo 'The launch daemon is enabled. Run'
381 | echo ''
382 | echo "launchctl load -w $service_directory$service_file"
383 | echo ''
384 | echo 'to start the daemon'
385 | else
386 | mv "$service_file" "$service_directory$service_file"
387 | chmod 600 "$service_directory$service_file"
388 | echo 'reloading launch daemon'
389 | launchctl unload "$service_directory$service_file"
390 | launchctl load -w "$service_directory$service_file"
391 | fi
392 | ;;
393 | *)
394 | mv "$service_file" "$service_directory$service_file"
395 | esac
396 |
397 | tar --extract --gunzip --file "${archive_name}" --strip-components 2 --directory "${configuration_directory}" "${archiveInternalName}config/"
398 | echo "A sample configuration is in '${configuration_directory}'. Remove the '.sample' extension and edit it as you see fit."
399 | echo "A sample logging file is in '${configuration_directory}'. Remove the '.sample' extension, reference it in the configuration and edit it as you see fit."
400 | fi
401 |
402 | echo 'delete archive' >&2
403 | rm "${archive_name}"
404 |
--------------------------------------------------------------------------------
/scripting-definition/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Eduard Wolf
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | alias(libs.plugins.kotlin.jvm)
19 | }
20 |
21 | group = "net.edwardday.serverscript"
22 |
23 | dependencies {
24 | implementation(libs.kotlin.scripting.common)
25 | implementation(libs.kotlin.scripting.jvm)
26 | implementation(libs.kotlin.scripting.dependencies)
27 | implementation(libs.kotlin.scripting.dependencies.maven)
28 | // coroutines dependency is required for this particular definition
29 | implementation(libs.kotlinx.coroutines)
30 | }
31 |
32 | kotlin {
33 | jvmToolchain(17)
34 | compilerOptions {
35 | allWarningsAsErrors.set(true)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/scripting-definition/src/main/kotlin/ScriptDefinition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Eduard Wolf
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.edwardday.serverscript.scriptdefinition
18 |
19 | import kotlinx.coroutines.runBlocking
20 | import net.edwardday.serverscript.scriptdefinition.annotation.Import
21 | import net.edwardday.serverscript.scriptdefinition.script.ServerScript
22 | import java.io.File
23 | import kotlin.script.experimental.annotations.KotlinScript
24 | import kotlin.script.experimental.api.RefineScriptCompilationConfigurationHandler
25 | import kotlin.script.experimental.api.ResultWithDiagnostics
26 | import kotlin.script.experimental.api.ScriptCollectedData
27 | import kotlin.script.experimental.api.ScriptCompilationConfiguration
28 | import kotlin.script.experimental.api.ScriptConfigurationRefinementContext
29 | import kotlin.script.experimental.api.ScriptDiagnostic
30 | import kotlin.script.experimental.api.ScriptSourceAnnotation
31 | import kotlin.script.experimental.api.asDiagnostics
32 | import kotlin.script.experimental.api.asSuccess
33 | import kotlin.script.experimental.api.collectedAnnotations
34 | import kotlin.script.experimental.api.defaultImports
35 | import kotlin.script.experimental.api.implicitReceivers
36 | import kotlin.script.experimental.api.importScripts
37 | import kotlin.script.experimental.api.onSuccess
38 | import kotlin.script.experimental.api.refineConfiguration
39 | import kotlin.script.experimental.dependencies.CompoundDependenciesResolver
40 | import kotlin.script.experimental.dependencies.DependsOn
41 | import kotlin.script.experimental.dependencies.FileSystemDependenciesResolver
42 | import kotlin.script.experimental.dependencies.Repository
43 | import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
44 | import kotlin.script.experimental.dependencies.resolveFromScriptSourceAnnotations
45 | import kotlin.script.experimental.host.FileBasedScriptSource
46 | import kotlin.script.experimental.host.toScriptSource
47 | import kotlin.script.experimental.jvm.dependenciesFromClassContext
48 | import kotlin.script.experimental.jvm.jvm
49 | import kotlin.script.experimental.jvm.updateClasspath
50 |
51 |
52 | const val SERVER_SCRIPT_FILE_EXTENSION = "server.kts"
53 |
54 | @KotlinScript(
55 | fileExtension = SERVER_SCRIPT_FILE_EXTENSION,
56 | compilationConfiguration = ServerScriptConfiguration::class,
57 | )
58 | abstract class ServerScriptDefinition
59 |
60 | class ServerScriptConfiguration : ScriptCompilationConfiguration(
61 | body = {
62 | defaultImports(DependsOn::class, Repository::class, Import::class)
63 | defaultImports("net.edwardday.serverscript.scriptdefinition.script.*")
64 | jvm {
65 | dependenciesFromClassContext(
66 | ServerScriptDefinition::class,
67 | "kotlin-scripting-dependencies",
68 | "scripting-definition",
69 | )
70 | }
71 | implicitReceivers(ServerScript::class)
72 | refineConfiguration {
73 | // the callback called when any of the listed file-level annotations are encountered in the compiled script
74 | // the processing is defined by the `handler`, that may return refined configuration depending on the annotations
75 | onAnnotations(DependsOn::class, Repository::class, handler = ServerScriptClasspathConfigurator())
76 | onAnnotations(Import::class, handler = ServerScriptImportsConfigurator())
77 | }
78 |
79 | }
80 | )
81 |
82 | class ServerScriptImportsConfigurator : RefineScriptCompilationConfigurationHandler {
83 | override fun invoke(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics {
84 | val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)
85 | ?.takeIf(List>::isNotEmpty)
86 | val importAnnotations = annotations?.filter { it.annotation is Import }?.takeIf(List<*>::isNotEmpty)
87 | ?: return context.compilationConfiguration.asSuccess()
88 | val workingDir = (context.script as? FileBasedScriptSource)?.file?.absoluteFile?.parentFile
89 | val diagnostics = arrayListOf()
90 | val scripts = importAnnotations.mapNotNull { (annotation, location) ->
91 | val path = (annotation as Import).path.let {
92 | if (it.endsWith(".$SERVER_SCRIPT_FILE_EXTENSION")) it else "$it.$SERVER_SCRIPT_FILE_EXTENSION"
93 | }
94 | val absolutePathFile = File(path).takeIf(File::isAbsolute)
95 | when {
96 | absolutePathFile != null -> absolutePathFile.takeIf(File::isFile).also {
97 | if (it == null) {
98 | diagnostics += ScriptDiagnostic(
99 | code = ScriptDiagnostic.unspecifiedError,
100 | message = "cannot find import script file at $path",
101 | severity = ScriptDiagnostic.Severity.WARNING,
102 | locationWithId = location,
103 | )
104 | }
105 | }
106 |
107 | workingDir != null -> File(workingDir, path).takeIf(File::isFile).also {
108 | if (it == null) {
109 | diagnostics += ScriptDiagnostic(
110 | code = ScriptDiagnostic.unspecifiedError,
111 | message = "cannot find import script file at ${workingDir.path}/$path",
112 | severity = ScriptDiagnostic.Severity.WARNING,
113 | locationWithId = location,
114 | )
115 | }
116 | }
117 |
118 | else -> {
119 | diagnostics += ScriptDiagnostic(
120 | code = ScriptDiagnostic.unspecifiedError,
121 | message = "cannot find import script file with relative path ($path) from a script with unknown location",
122 | severity = ScriptDiagnostic.Severity.ERROR,
123 | locationWithId = location,
124 | )
125 | null
126 | }
127 | }?.toScriptSource()
128 | }
129 |
130 | return if (diagnostics.isNotEmpty()) {
131 | ResultWithDiagnostics.Failure(diagnostics)
132 | } else {
133 | ScriptCompilationConfiguration(context.compilationConfiguration) {
134 | importScripts(scripts)
135 | }.asSuccess()
136 | }
137 | }
138 |
139 | }
140 |
141 | class ServerScriptClasspathConfigurator : RefineScriptCompilationConfigurationHandler {
142 | private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
143 |
144 | override operator fun invoke(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics =
145 | processAnnotations(context)
146 |
147 | private fun processAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics {
148 | val diagnostics = arrayListOf()
149 |
150 | val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)
151 | ?.takeIf(List<*>::isNotEmpty)
152 | ?: return context.compilationConfiguration.asSuccess()
153 |
154 | val resolveResult = try {
155 | runBlocking {
156 | resolver.resolveFromScriptSourceAnnotations(
157 | annotations.filter { it.annotation is DependsOn || it.annotation is Repository }
158 | )
159 | }
160 | } catch (e: Throwable) {
161 | ResultWithDiagnostics.Failure(
162 | *diagnostics.toTypedArray(),
163 | e.asDiagnostics(path = context.script.locationId)
164 | )
165 | }
166 |
167 | return resolveResult.onSuccess { resolvedClassPath ->
168 | ScriptCompilationConfiguration(context.compilationConfiguration) {
169 | updateClasspath(resolvedClassPath)
170 | }.asSuccess()
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/scripting-definition/src/main/kotlin/annotation/Import.kt:
--------------------------------------------------------------------------------
1 | package net.edwardday.serverscript.scriptdefinition.annotation
2 |
3 | @Target(AnnotationTarget.FILE)
4 | @Repeatable
5 | @Retention(AnnotationRetention.SOURCE)
6 | annotation class Import(val path: String)
7 |
--------------------------------------------------------------------------------
/scripting-definition/src/main/kotlin/script/ServerScript.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Eduard Wolf
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.edwardday.serverscript.scriptdefinition.script
18 |
19 | import java.nio.file.Path
20 |
21 | interface ServerScript {
22 | val parameters: Map>
23 | val cache: Cache
24 |
25 | fun path(name: String): Path
26 |
27 | fun setHeaders(key: String, value: List)
28 | fun getHeaders(key: String): List
29 | fun readInputLine(): String?
30 | fun writeOutput(output: String)
31 | fun writeError(output: String)
32 | }
33 |
34 | interface Cache {
35 | fun getOrSet(key: Any?, factory: () -> T): T
36 | fun updateOrSet(key: Any?, factory: () -> T, merge: (oldValue: T) -> T): T
37 | }
38 |
39 | fun ServerScript.setHeader(key: String, vararg values: String) {
40 | setHeaders(key, values.toList())
41 | }
42 |
43 | fun ServerScript.addHeader(key: String, vararg value: String) {
44 | setHeaders(key, getHeaders(key) + value.asList())
45 | }
46 |
47 | fun ServerScript.status(statusCode: Int) {
48 | val value = when (statusCode) {
49 | 100 -> "100 Continue"
50 | 101 -> "101 Switching Protocols"
51 | 102 -> "102 Processing"
52 | 103 -> "103 Early Hints"
53 |
54 | 200 -> "200 OK"
55 | 201 -> "201 Created"
56 | 202 -> "202 Accepted"
57 | 203 -> "203 Non-Authoritative Information"
58 | 204 -> "204 No Content"
59 | 205 -> "205 Reset Content"
60 | 206 -> "206 Partial Content"
61 | 207 -> "207 Multi-Status"
62 | 208 -> "208 Already Reported"
63 | 226 -> "226 IM Used"
64 |
65 | 300 -> "300 Multiple Choices"
66 | 301 -> "301 Moved Permanently"
67 | 302 -> "302 Found"
68 | 303 -> "303 See Other"
69 | 304 -> "304 Not Modified"
70 | 305 -> "305 Use Proxy Deprecated"
71 | 306 -> "306 unused"
72 | 307 -> "307 Temporary Redirect"
73 | 308 -> "308 Permanent Redirect"
74 |
75 | 400 -> "400 Bad Request"
76 | 401 -> "401 Unauthorized"
77 | 402 -> "402 Payment Required Experimental"
78 | 403 -> "403 Forbidden"
79 | 404 -> "404 Not Found"
80 | 405 -> "405 Method Not Allowed"
81 | 406 -> "406 Not Acceptable"
82 | 407 -> "407 Proxy Authentication Required"
83 | 408 -> "408 Request Timeout"
84 | 409 -> "409 Conflict"
85 | 410 -> "410 Gone"
86 | 411 -> "411 Length Required"
87 | 412 -> "412 Precondition Failed"
88 | 413 -> "413 Payload Too Large"
89 | 414 -> "414 URI Too Long"
90 | 415 -> "415 Unsupported Media Type"
91 | 416 -> "416 Range Not Satisfiable"
92 | 417 -> "417 Expectation Failed"
93 | 418 -> "418 I'm a teapot"
94 | 421 -> "421 Misdirected Request"
95 | 422 -> "422 Unprocessable Content"
96 | 423 -> "423 Locked"
97 | 424 -> "424 Failed Dependency"
98 | 425 -> "425 Too Early Experimental"
99 | 426 -> "426 Upgrade Required"
100 | 428 -> "428 Precondition Required"
101 | 429 -> "429 Too Many Requests"
102 | 431 -> "431 Request Header Fields Too Large"
103 | 451 -> "451 Unavailable For Legal Reasons"
104 |
105 | 500 -> "500 Internal Server Error"
106 | 501 -> "501 Not Implemented"
107 | 502 -> "502 Bad Gateway"
108 | 503 -> "503 Service Unavailable"
109 | 504 -> "504 Gateway Timeout"
110 | 505 -> "505 HTTP Version Not Supported"
111 | 506 -> "506 Variant Also Negotiates"
112 | 507 -> "507 Insufficient Storage"
113 | 508 -> "508 Loop Detected"
114 | 510 -> "510 Not Extended"
115 | 511 -> "511 Network Authentication Required"
116 | else -> statusCode.toString()
117 | }
118 | setHeader("Status", value)
119 | }
--------------------------------------------------------------------------------
/scripting-host/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.jvm.component.internal.JvmSoftwareComponentInternal
2 |
3 | /*
4 | * Copyright 2024 Eduard Wolf
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | plugins {
20 | alias(libs.plugins.kotlin.jvm)
21 | application
22 | }
23 |
24 | group = "net.edwardday.serverscript"
25 | version = findProperty("version") as String
26 |
27 | dependencies {
28 | implementation(libs.kotlin.scripting.common)
29 | implementation(libs.kotlin.scripting.jvm)
30 | implementation(libs.kotlin.scripting.jvm.host)
31 | implementation(project(":scripting-definition")) // the script definition module
32 |
33 | implementation(libs.clikt)
34 | implementation(libs.okio)
35 | implementation(libs.kotlinx.coroutines)
36 | implementation(libs.kotlinx.coroutines.slf4j)
37 | implementation(libs.oshai.logging)
38 | runtimeOnly(libs.logback.logger)
39 |
40 | testImplementation(libs.kotlin.test)
41 | testImplementation(libs.kotlinx.coroutines.test)
42 | }
43 |
44 | kotlin {
45 | jvmToolchain(17)
46 | compilerOptions {
47 | allWarningsAsErrors.set(true)
48 | }
49 | }
50 |
51 | val createReleaseStartScript by tasks.registering {
52 | val startScriptsTask = tasks.named("startScripts")
53 | dependsOn(startScriptsTask)
54 | val outputDirectory = layout.buildDirectory.dir("dist/release/scripts")
55 | outputs.dir(outputDirectory)
56 | doLast {
57 | val directory = outputDirectory.get().asFile
58 | startScriptsTask.get().outputs.files.flatMap {
59 | if (it.isFile) listOf(it) else it.listFiles()?.asList().orEmpty()
60 | }.map { script ->
61 | val classpathLineStart: String
62 | val replaceOriginal: String
63 | val replaceNewValue: String
64 | if (script.extension == "bat") {
65 | classpathLineStart = "set CLASSPATH="
66 | replaceOriginal = "%APP_HOME%\\lib\\"
67 | replaceNewValue = "%APP_HOME%\\kss_lib\\"
68 | } else {
69 | classpathLineStart = "CLASSPATH="
70 | replaceOriginal = "\$APP_HOME/lib/"
71 | replaceNewValue = "\$APP_HOME/kss_lib/"
72 | }
73 | File(directory, script.name).also { file ->
74 | script.bufferedReader().useLines { scriptLines ->
75 | file.bufferedWriter().use { writer ->
76 | scriptLines.forEach { line ->
77 | val updatedLine = if (!line.startsWith(classpathLineStart)) {
78 | line
79 | } else {
80 | line.replace(replaceOriginal, replaceNewValue)
81 | }
82 | writer.appendLine(updatedLine)
83 | }
84 | }
85 | }
86 | if (file.extension != "bat") {
87 | file.setExecutable(true, false)
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | distributions {
95 | create("release") {
96 | contents {
97 | into("bin") {
98 | from(createReleaseStartScript)
99 | into("kss_lib") {
100 | from(tasks.getByName("jar"))
101 | from((components["java"] as JvmSoftwareComponentInternal).mainFeature.runtimeClasspathConfiguration)
102 | }
103 | }
104 | from("src/release")
105 | }
106 | }
107 | }
108 |
109 | tasks.named("releaseDistTar") {
110 | compression = Compression.GZIP
111 | archiveExtension.set("tar.gz")
112 | }
113 |
114 | application {
115 | mainClass.set("net.edwardday.serverscript.scripthost.MainKt")
116 | applicationName = "kss"
117 | executableDir = ""
118 | }
119 |
120 | val processResourceVersionFile by tasks.registering {
121 | inputs.property("version", project.version)
122 | val outputFile = layout.buildDirectory.file("resources/main/version.properties")
123 | outputs.file(outputFile)
124 | doLast {
125 | val version = requireNotNull(inputs.properties["version"])
126 | outputFile.get().asFile.printWriter().use {
127 | it.print("version=$version")
128 | }
129 | }
130 | }
131 |
132 | tasks.processResources {
133 | dependsOn(processResourceVersionFile)
134 | }
135 |
--------------------------------------------------------------------------------
/scripting-host/src/main/kotlin/CacheImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Eduard Wolf
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package net.edwardday.serverscript.scripthost
18 |
19 | import java.util.concurrent.atomic.AtomicReference
20 | import net.edwardday.serverscript.scriptdefinition.script.Cache
21 |
22 | class CacheImpl : Cache {
23 | private val map = AtomicReference