diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 587a79b..0f51b55 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,5 +1,5 @@
# https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository
-# I need to apply here: https://github.com/sponsors
+# Maybe apply here: https://github.com/sponsors
# github: [alexandru]
patreon: alexelcu
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ccf49a2..7884dd6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,31 +1,29 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+# GitHub recommends pinning actions to a commit SHA.
+# To get a newer version, you will need to update the SHA.
+# You can also reference a tag or branch, but the action may change without warning.
+
name: build
on: [push, pull_request]
jobs:
build:
- runs-on: ubuntu-20.04
-
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@master
- - uses: haskell/actions/setup@v1
- with:
- ghc-version: '8.10.7' # lts-18.21?
- enable-stack: true
- stack-version: 'latest'
+ - uses: actions/checkout@v3
- - uses: actions/cache@v2
- name: Cache ~/.stack
+ - name: Set up JDK
+ uses: actions/setup-java@v3
with:
- key: ${{ runner.os }}-stack-${{ hashFiles('**/stack.yaml.lock') }}-${{ hashFiles('**/github-webhook-listener.cabal') }}
- restore-keys: |
- ${{ runner.os }}-stack-
- path: |
- ~/.stack
+ java-version: '17'
+ distribution: 'adopt'
- - name: Resolve/Update Dependencies
- run: |
- stack --no-terminal setup
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
- - name: Run tests
- run: |
- stack --no-terminal test
+ - name: Run Tests
+ run: ./gradlew check
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 5661ab3..b8ff98a 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -4,34 +4,75 @@ on:
types: [released]
jobs:
- deploy_docker:
- runs-on: ubuntu-20.04
+ deploy_docker_jvm:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install Docker
+ run: |
+ sudo apt-get remove docker docker-engine docker.io containerd runc
+ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+ sudo apt-get update
+ sudo apt-get install docker-ce docker-ce-cli containerd.io
+
+ - name: Login to Docker
+ run: docker login ghcr.io --username=alexandru --password="$GH_TOKEN"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build JVM Docker image
+ run: |
+ make build-jvm
+ env:
+ GIT_TAG: ${{ github.ref }}
+
+ - name: Push JVM Docker images
+ run: |
+ make push-jvm
+ env:
+ GIT_TAG: ${{ github.ref }}
+
+ deploy_docker_native:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install Docker
+ run: |
+ sudo apt-get remove docker docker-engine docker.io containerd runc
+ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+ sudo apt-get update
+ sudo apt-get install docker-ce docker-ce-cli containerd.io
+
+ - name: Login to Docker
+ run: docker login ghcr.io --username=alexandru --password="$GH_TOKEN"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build Native Docker image
+ run: |
+ make build-native
+ env:
+ GIT_TAG: ${{ github.ref }}
+
+ - name: Push Native Docker images
+ run: |
+ make push-native
+ env:
+ GIT_TAG: ${{ github.ref }}
+ all:
+ name: Pushed All
+ if: always()
+ needs: [ deploy_docker_native, deploy_docker_jvm ]
+ runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@master
-
- - name: Install Docker
- run: |
- sudo apt-get remove docker docker-engine docker.io containerd runc
- sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- sudo apt-get update
- sudo apt-get install docker-ce docker-ce-cli containerd.io
-
- - name: Login to Docker
- run: docker login --username=$DOCKER_USER --password=$DOCKER_PASS
- env:
- DOCKER_USER: ${{ secrets.DOCKER_USER }}
- DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
-
- - name: Build Docker image
- run: |
- make build
- env:
- GIT_TAG: ${{ github.ref }}
-
- - name: Push Docker image
- run: |
- make push
-
\ No newline at end of file
+ - name: Validate required tests
+ uses: re-actors/alls-green@release/v1
+ with:
+ jobs: ${{ toJSON(needs) }}
diff --git a/.github/workflows/refreshVersions.yml b/.github/workflows/refreshVersions.yml
new file mode 100644
index 0000000..12a945b
--- /dev/null
+++ b/.github/workflows/refreshVersions.yml
@@ -0,0 +1,55 @@
+# Worfklow for https://jmfayard.github.io/refreshVersions/
+
+name: RefreshVersions
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 7 * * 1'
+
+jobs:
+ "Refresh-Versions":
+ runs-on: "ubuntu-latest"
+ steps:
+ - id: step-0
+ name: check-out
+ uses: actions/checkout@v3
+ with:
+ ref: main
+ - id: step-1
+ name: setup-java
+ uses: actions/setup-java@v3
+ with:
+ java-version: 17
+ distribution: adopt
+ - id: step-2
+ name: create-branch
+ uses: peterjgrainger/action-create-branch@v2.2.0
+ with:
+ branch: dependency-update
+ env:
+ GITHUB_TOKEN: {{ secrets.GITHUB_TOKEN }}
+ - id: step-3
+ name: gradle refreshVersions
+ uses: gradle/gradle-build-action@v2
+ with:
+ arguments: refreshVersions
+ - id: step-4
+ name: Commit
+ uses: EndBug/add-and-commit@v9
+ with:
+ author_name: GitHub Actions
+ author_email: noreply@github.com
+ message: Refresh versions.properties
+ new_branch: dependency-update
+ push: --force --set-upstream origin dependency-update
+ - id: step-5
+ name: Pull Request
+ uses: repo-sync/pull-request@v2
+ with:
+ source_branch: dependency-update
+ destination_branch: main
+ pr_title: Upgrade gradle dependencies
+ pr_body: '[refreshVersions](https://github.com/jmfayard/refreshVersions) has found those library updates!'
+ pr_draft: true
+ github_token: {{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index e720099..c426c32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,36 @@
-dist
-dist-*
-cabal-dev
-*.o
-*.hi
-*.chi
-*.chs.h
-*.dyn_o
-*.dyn_hi
-.hpc
-.hsenv
-.cabal-sandbox/
-cabal.sandbox.config
-*.prof
-*.aux
-*.hp
-*.eventlog
-.stack-work/
-cabal.project.local
-cabal.project.local~
-.HTF/
-.ghc.environment.*
-.vscode/
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
\ No newline at end of file
diff --git a/.sdkmanrc b/.sdkmanrc
new file mode 100644
index 0000000..239718e
--- /dev/null
+++ b/.sdkmanrc
@@ -0,0 +1,3 @@
+# Enable auto-env through the sdkman_auto_env config
+# Add key=value pairs of SDKs to use below
+java=22.2.r17-grl
diff --git a/ChangeLog.md b/ChangeLog.md
deleted file mode 100644
index 6b1b76a..0000000
--- a/ChangeLog.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Changelog for github-webhook-listener
-
-## Unreleased changes
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 003931f..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,28 +0,0 @@
-FROM fpco/stack-build:lts-18.21 as build
-
-COPY . /opt/build/
-
-WORKDIR /opt/build
-
-RUN stack build --system-ghc
-
-RUN mv "$(stack path --local-install-root --system-ghc)/bin" /opt/build/bin
-
-# -------------------------------------------------------------------------------------------
-# Base image for stack build so compiled artifact from previous
-# stage should run
-FROM ubuntu:18.04 as app
-RUN mkdir -p /opt/app
-WORKDIR /opt/app
-
-RUN apt-get update
-RUN apt-get install git curl jq -y
-RUN apt-get upgrade -y
-RUN apt-get autoremove -y
-
-COPY --from=build /opt/build/bin .
-
-RUN mkdir -p /opt/app/config
-COPY ./resources/config-sample.yaml /opt/app/config/config.yaml
-
-CMD [ "/opt/app/github-webhook-listener-exe", "-c", "/opt/app/config/config.yaml" ]
diff --git a/Dockerfile.fromCurrent b/Dockerfile.fromCurrent
deleted file mode 100644
index 38a2000..0000000
--- a/Dockerfile.fromCurrent
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM alexelcu/github-webhook-listener:latest as app
-
-RUN mkdir -p /opt/app/config
-COPY ./resources/config-sample.yaml /opt/app/config/config.yaml
-
-CMD [ "/opt/app/github-webhook-listener-exe", "-c", "/opt/app/config/config.yaml" ]
diff --git a/LICENSE b/LICENSE
index e2c71ac..0ad25db 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,30 +1,661 @@
-Copyright Alexandru Nedelcu (c) 2018
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
-
- * Neither the name of Alexandru Nedelcu nor the names of other
- contributors may be used to endorse or promote products derived
- from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/Makefile b/Makefile
index 1fe9935..94a6ac3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,26 @@
-NAME := alexelcu/github-webhook-listener
-TAG := $$(./scripts/new-version)
-IMG := ${NAME}:${TAG}
-LATEST := ${NAME}:latest
+NAME := ghcr.io/alexandru/github-webhook-listener
+TAG := $$(./scripts/new-version.sh)
+IMG_JVM := ${NAME}:jvm-${TAG}
+IMG_NATIVE := ${NAME}:native-${TAG}
+LATEST_JVM := ${NAME}:jvm-latest
+LATEST_NATIVE := ${NAME}:native-latest
+LATEST := ${NAME}:latest
-build:
- docker build -t "${IMG}" .
- docker tag "${IMG}" "${LATEST}"
+build-jvm:
+ docker build -f ./src/main/docker/Dockerfile.jvm -t "${IMG_JVM}" .
+ docker tag "${IMG_JVM}" "${LATEST_JVM}"
+
+push-jvm:
+ docker push ${IMG_JVM}
+ docker push ${LATEST_JVM}
+
+build-native:
+ docker build -f ./src/main/docker/Dockerfile.native -t "${IMG_NATIVE}" .
+ docker tag "${IMG_NATIVE}" "${LATEST_NATIVE}"
+ docker tag "${IMG_NATIVE}" "${LATEST}"
+
+push-native:
+ docker push ${IMG_NATIVE}
+ docker push ${LATEST_NATIVE}
+ docker push ${LATEST}
-push:
- docker push ${NAME}
diff --git a/README.md b/README.md
index b3ecaad..9c8c2ac 100644
--- a/README.md
+++ b/README.md
@@ -1,135 +1,54 @@
-# github-webhook-listener
+# GitHub Webhook Listener (ver. 2)
-[![Build](https://github.com/alexandru/github-webhook-listener/workflows/build/badge.svg?branch=master)](https://github.com/alexandru/github-webhook-listener/actions?query=branch%3Amaster+workflow%3Abuild) [![Deploy](https://github.com/alexandru/github-webhook-listener/workflows/deploy/badge.svg)](https://github.com/alexandru/github-webhook-listener/actions?query=workflow%3Adeploy)
+[![Build](https://github.com/alexandru/github-webhook-listener/workflows/build/badge.svg?branch=main)](https://github.com/alexandru/github-webhook-listener/actions?query=branch%3Amain+workflow%3Abuild) [![Deploy](https://github.com/alexandru/github-webhook-listener/workflows/deploy/badge.svg)](https://github.com/alexandru/github-webhook-listener/actions?query=workflow%3Adeploy)
A simple web app that can be registered as a
[GitHub Webhook](https://developer.github.com/webhooks/)
and trigger shell commands in response to events.
Main use-case is to trigger refreshes of websites hosted on your own
-server via [Travis-CI](https://travis-ci.org/) jobs, but in a secure
-way, without exposing server credentials or SSH keys.
+server via CI jobs (e.g., GitHub Actions), but in a secure way, without
+exposing server credentials or SSH keys.
The server process is also light in resource usage, not using more
-than 20 MB of RAM on a 64-bit Ubuntu machine, so it can be installed
-on under-powered servers.
+than 20 MB of RAM, so it can be installed on under-powered servers.
-## Setup
+## Development
-Images are being pushed on [Docker Hub](https://hub.docker.com/repository/docker/alexelcu/github-webhook-listener) and you can quickly run the process like this:
+To run the project in development mode:
```sh
-docker run \
- -p 8080:8080 \
- -ti alexelcu/github-webhook-listener
+./gradlew run -Pdevelopment --args="./config/application-dummy.conf"
```
-### Server Configuration
+After adding new dependencies:
-On its own this just starts the server, but doesn't know how to do anything. We'll need to specify a configuration file: Create your `./config.yaml`:
-
-```yaml
-http:
- path: "/"
- port: 8080
-
-runtime:
- workers: 2
- output: stdout
-
-projects:
- myproject:
- ref: "refs/heads/gh-pages"
- directory: "/var/www/myproject"
- command: "git pull"
- secret: "xxxxxxxxxxxxxxxxxxxxxxxxxx"
-```
-
-Notes:
-
-1. `myproject` in `project.myproject` is just a name of a project, it could be anything
-2. `ref` says to only react on pushes to the `gh-pages` branch
-3. `directory` is where the `command` should be executed
-4. `command` is to be executed — note that `git` is already installed in the Docker image, doing `git pull` on a directory being the primary use case
-
-It would be better if the `git pull` command would update files using a specified host user and group. And we'll also need an SSH key to install our "deployment key". So on your Linux box:
-
-```
-sudo adduser synchronize
-
-sudo adduser synchronize www-data
-
-sudo chown -R synchornize:www-data /var/www/myproject
+```sh
+./gradlew refreshVersionsMigrate --mode=VersionCatalogOnly
```
-And afterwards:
+To update project dependencies:
```sh
-docker run \
- -p 8080:8080 \
- -v "$(pwd)/config.yaml:/opt/app/config/config.yaml" \
- -u "$(id -u synchronize):$(id -g synchronize)" \
- -ti alexelcu/github-webhook-listener
+./gradlew refreshVersions
```
-You could also use [docker-compose](https://docs.docker.com/compose/), here's what I have on my own server:
-
-```yaml
-version: '3.3'
-
-services:
- github-webhook-listener:
- container_name: github-webhook-listener
- image: 'alexelcu/github-webhook-listener:latest'
- restart: unless-stopped
- ports:
- - "8080:8080"
- tty: true
- networks:
- - main
- volumes:
- - /var/www:/var/www
- - /etc/github-webhook-listener/config.yaml:/opt/app/config/config.yaml
- user: "${SYNC_UID}:${SYNC_GID}"
-
-networks:
- main:
- external:
- name: main
-```
+## Docker
-Then to expose this server via [Nginx](https://www.nginx.com/), it's just a matter of configuring a `proxy_pass`:
+To build the JVM image from scratch:
-```conf
-location / {
- proxy_pass http://127.0.0.1:8080;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-for $remote_addr;
- proxy_connect_timeout 300;
-}
+```sh
+docker build -f ./src/main/docker/Dockerfile.jvm -t github-webhook-listener-jvm .
```
-### Configuring Your GitHub Project
-
-Go to the settings page of your project, the "Webhooks" section, link
-should be like: `https://github.com///settings/hooks`
-
-Setup screen for adding a new Webhook should look like this:
-
-![Webhook setup screen](https://github.com/alexandru/github-webhook-listener/wiki/setup.png)
+To run the JVM image:
-NOTEs on those fields:
-
-1. the Payload URL contains a `some-id`, in the described path, that should be configured in your `config.yaml` file to identify your project
-2. the Secret is the passphrase you also configured in `config.yaml` — this is optional, but if the `config.yaml` mentions a passphrase which you're not mentioning in this setup, then requests will fail
-
-### Manual Setup (without Docker)
-
-[Wiki instructions for Setup](https://github.com/alexandru/github-webhook-listener/wiki/Setup)
+```sh
+docker run -p 8080:8080 github-webhook-listener-jvm
+```
## License
Copyright © 2018-2022 Alexandru Nedelcu, some rights reserved.
-Licensed under the 3-Clause BSD License. See [LICENSE](./LICENSE).
+Licensed under the AGPL-3.0 license. See [LICENSE](./LICENSE).
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
index 9a994af..0000000
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
deleted file mode 100644
index 30b82d0..0000000
--- a/app/Main.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Main where
-
-import CmdLine
-import Server (run)
-import System.IO (stdout, stderr, withFile, IOMode(AppendMode))
-import qualified AppConfig as Cfg
-
-main :: IO ()
-main = do
- args <- getCmdLineArgs
- appConfig <- Cfg.readAppConfig (configPath args)
- let output = Cfg.output . Cfg.runtime $ appConfig
- case output of
- "stdout" ->
- run appConfig stdout
- "stderr" ->
- run appConfig stderr
- path ->
- withFile path AppendMode (run appConfig)
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..d76966f
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,83 @@
+plugins {
+ application
+ kotlin("jvm")
+ id("io.ktor.plugin")
+ id("org.jetbrains.kotlin.plugin.serialization")
+ // See https://github.com/JLLeitschuh/ktlint-gradle
+ id("org.jlleitschuh.gradle.ktlint")
+ // https://graalvm.github.io/native-build-tools/0.9.14/gradle-plugin.html
+ id("org.graalvm.buildtools.native")
+}
+
+group = "org.alexn.hook"
+version = "0.0.1"
+
+application {
+ mainClass.set("org.alexn.hook.MainKt")
+
+ val isDevelopment: Boolean = project.ext.has("development")
+ applicationDefaultJvmArgs = listOf(
+ "-Dio.ktor.development=$isDevelopment",
+ // https://www.graalvm.org/22.0/reference-manual/native-image/Agent/
+ // "-agentlib:native-image-agent=config-output-dir=./src/main/resources/META-INF/native-image"
+ )
+}
+
+// https://ktor.io/docs/graalvm.html#execute-the-native-image-tool
+// https://github.com/ktorio/ktor-samples/blob/main/graalvm/build.gradle.kts
+graalvmNative {
+ binaries {
+ named("main") {
+ fallback.set(false)
+ verbose.set(true)
+
+ buildArgs.add("--initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback")
+ buildArgs.add("--initialize-at-build-time=io.ktor,kotlinx,kotlin")
+
+ buildArgs.add("-H:+InstallExitHandlers")
+ buildArgs.add("-H:+ReportUnsupportedElementsAtRuntime")
+ buildArgs.add("-H:+ReportExceptionStackTraces")
+ buildArgs.add("--no-fallback")
+
+ imageName.set("github-webhook-listener")
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+ implementation(libs.logback.classic)
+ implementation(libs.kaml)
+ implementation(libs.commons.codec)
+ implementation(libs.arrow.core)
+ implementation(libs.arrow.fx.coroutines)
+ implementation(libs.arrow.fx.stm)
+ implementation(libs.ktor.serialization.kotlinx.json)
+ implementation(libs.ktor.server.content.negotiation)
+ implementation(libs.ktor.server.core)
+ // implementation(libs.ktor.server.netty)
+ implementation(libs.ktor.server.cio)
+ implementation(libs.ktor.server.tests.jvm)
+ implementation(libs.commons.text)
+ implementation(libs.kotlin.test.junit)
+ implementation(libs.kotlinx.cli)
+ implementation(libs.kotlinx.serialization.hocon)
+ implementation(libs.kotlinx.serialization.json)
+}
+
+tasks {
+ withType {
+ kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
+ kotlinOptions.javaParameters = true
+ }
+}
+
+ktor {
+ fatJar {
+ archiveFileName.set("github-webhook-listener-fat.jar")
+ }
+}
diff --git a/config/application-dummy.yaml b/config/application-dummy.yaml
new file mode 100644
index 0000000..e63d99f
--- /dev/null
+++ b/config/application-dummy.yaml
@@ -0,0 +1,13 @@
+http:
+ host: "0.0.0.0"
+ port: 8080
+ path: "/"
+
+projects:
+ myproject:
+ action: "push"
+ ref: "refs/heads/gh-pages"
+ directory: "/tmp"
+ command: "touch ./i-was-here.txt"
+ timeout: "PT5S"
+ secret: "xxxxxxxxxxxxxxxxxxxxxxxxxx"
diff --git a/github-webhook-listener.cabal b/github-webhook-listener.cabal
deleted file mode 100644
index 928d459..0000000
--- a/github-webhook-listener.cabal
+++ /dev/null
@@ -1,93 +0,0 @@
-cabal-version: 1.12
-
--- This file has been generated from package.yaml by hpack version 0.31.2.
---
--- see: https://github.com/sol/hpack
---
--- hash: e1f1443ff11518d2be450b6954b3705d939bd3d4e06133ab1be12dd24564265c
-
-name: github-webhook-listener
-version: 0.1.0.3
-description: Please see the README on GitHub at
-homepage: https://github.com/alexandru/github-webhook-listener#readme
-bug-reports: https://github.com/alexandru/github-webhook-listener/issues
-author: Alexandru Nedelcu
-maintainer: noreply@alexn.org
-copyright: 2018-2022 Alexandru Nedelcu
-license: BSD3
-license-file: LICENSE
-build-type: Simple
-extra-source-files:
- README.md
- ChangeLog.md
-data-files:
- resources/*.yaml
-
-source-repository head
- type: git
- location: https://github.com/alexandru/github-webhook-listener
-
-library
- other-modules:
- Paths_github_webhook_listener
- hs-source-dirs:
- src
- default-language: Haskell2010
- ghc-options: -Wall -Werror
- build-depends:
- base >=4.7 && <5
- , SHA
- , aeson
- , blaze-builder
- , bytestring
- , containers
- , directory
- , filelock
- , filepath
- , http-types
- , optparse-applicative
- , regex-compat
- , scotty
- , shelly
- , text
- , time
- , yaml
-
- exposed-modules:
- AppConfig
- , CmdLine
- , Command
- , Controller
- , Logger
- , Payload
- , Server
-
-executable github-webhook-listener-exe
- main-is: Main.hs
- other-modules:
- Paths_github_webhook_listener
- hs-source-dirs:
- app
- default-language: Haskell2010
- ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Werror
- build-depends:
- base >=4.12 && <5
- , github-webhook-listener
-
-test-suite github-webhook-listener-test
- type: exitcode-stdio-1.0
- main-is: Main.hs
- other-modules:
- Paths_github_webhook_listener
- , AppConfigSpec
- hs-source-dirs:
- test
- default-language: Haskell2010
- ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Werror
- build-depends:
- base >=4.12 && <5
- , containers
- , filepath
- , github-webhook-listener
- , hspec
-
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..97c2bb6
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,166 @@
+## Generated by $ ./gradlew refreshVersionsCatalog
+
+[plugins]
+
+org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.7.20" }
+
+io-ktor-plugin = { id = "io.ktor.plugin", version = "2.1.2" }
+
+org-jetbrains-kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version = "1.7.20" }
+
+org-jlleitschuh-gradle-ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "11.0.0" }
+
+org-graalvm-buildtools-native = { id = "org.graalvm.buildtools.native", version = "0.9.14" }
+
+[versions]
+
+kotlin = "1.7.20"
+
+arrow = "1.1.2"
+## ⬆ = "1.1.3-alpha.1"
+## ⬆ = "1.1.3-alpha.2"
+## ⬆ = "1.1.3-alpha.3.0+2022-05-16T16-21-58-758705Z"
+## ⬆ = "1.1.3-alpha.4.0+2022-05-17T09-11-10-723810Z"
+## ⬆ = "1.1.3-alpha.5.0+2022-05-17T11-44-11-714740Z"
+## ⬆ = "1.1.3-alpha.6"
+## ⬆ = "1.1.3-alpha.7"
+## ⬆ = "1.1.3-alpha.8"
+## ⬆ = "1.1.3-alpha.9"
+## ⬆ = "1.1.3-alpha.10"
+## ⬆ = "1.1.3-alpha.11"
+## ⬆ = "1.1.3-alpha.12"
+## ⬆ = "1.1.3-alpha.13"
+## ⬆ = "1.1.3-alpha.14"
+## ⬆ = "1.1.3-alpha.15"
+## ⬆ = "1.1.3-alpha.16"
+## ⬆ = "1.1.3-alpha.17"
+## ⬆ = "1.1.3-alpha.18"
+## ⬆ = "1.1.3-alpha.19"
+## ⬆ = "1.1.3-alpha.20"
+## ⬆ = "1.1.3-alpha.21"
+## ⬆ = "1.1.3-alpha.22"
+## ⬆ = "1.1.3-alpha.23"
+## ⬆ = "1.1.3-alpha.24"
+## ⬆ = "1.1.3-alpha.25"
+## ⬆ = "1.1.3-alpha.26"
+## ⬆ = "1.1.3-alpha.27"
+## ⬆ = "1.1.3-alpha.28"
+## ⬆ = "1.1.3-alpha.29"
+## ⬆ = "1.1.3-alpha.30"
+## ⬆ = "1.1.3-alpha.31"
+## ⬆ = "1.1.3-alpha.32"
+## ⬆ = "1.1.3-alpha.33"
+## ⬆ = "1.1.3-alpha.34"
+## ⬆ = "1.1.3-alpha.35"
+## ⬆ = "1.1.3-alpha.36"
+## ⬆ = "1.1.3-alpha.37"
+## ⬆ = "1.1.3-alpha.38"
+## ⬆ = "1.1.3-alpha.39"
+## ⬆ = "1.1.3-alpha.40"
+## ⬆ = "1.1.3-alpha.41"
+## ⬆ = "1.1.3-alpha.42"
+## ⬆ = "1.1.3-alpha.43"
+## ⬆ = "1.1.3-alpha.44"
+## ⬆ = "1.1.3-alpha.45"
+## ⬆ = "1.1.3-alpha.46"
+## ⬆ = "1.1.3-alpha.47"
+## ⬆ = "1.1.3-alpha.48"
+## ⬆ = "1.1.3-alpha.49"
+## ⬆ = "1.1.3-alpha.50"
+## ⬆ = "1.1.3-alpha.51"
+## ⬆ = "1.1.3-alpha.52"
+## ⬆ = "1.1.3-rc.1"
+## ⬆ = "1.1.3"
+## ⬆ = "1.1.4-alpha.1"
+## ⬆ = "1.1.4-alpha.2"
+## ⬆ = "1.1.4-alpha.3"
+## ⬆ = "1.1.4-alpha.4"
+## ⬆ = "1.1.4-alpha.5"
+## ⬆ = "1.1.4-alpha.6"
+## ⬆ = "1.1.4-alpha.7"
+
+ktor = "2.1.2"
+
+kotlinx-cli = "0.3.5"
+
+kotlinx-serialization = "1.4.0"
+
+[libraries]
+
+logback-classic = "ch.qos.logback:logback-classic:1.2.11"
+## ⬆ :1.3.0-alpha0"
+## ⬆ :1.3.0-alpha1"
+## ⬆ :1.3.0-alpha2"
+## ⬆ :1.3.0-alpha3"
+## ⬆ :1.3.0-alpha4"
+## ⬆ :1.3.0-alpha5"
+## ⬆ :1.3.0-alpha6"
+## ⬆ :1.3.0-alpha7"
+## ⬆ :1.3.0-alpha8"
+## ⬆ :1.3.0-alpha9"
+## ⬆ :1.3.0-alpha10"
+## ⬆ :1.3.0-alpha11"
+## ⬆ :1.3.0-alpha12"
+## ⬆ :1.3.0-alpha13"
+## ⬆ :1.3.0-alpha14"
+## ⬆ :1.3.0-alpha15"
+## ⬆ :1.3.0-alpha16"
+## ⬆ :1.3.0-beta0"
+## ⬆ :1.3.0"
+## ⬆ :1.3.1"
+## ⬆ :1.3.2"
+## ⬆ :1.3.3"
+## ⬆ :1.3.4"
+## ⬆ :1.4.0"
+## ⬆ :1.4.1"
+## ⬆ :1.4.2"
+## ⬆ :1.4.3"
+## ⬆ :1.4.4"
+
+kaml = "com.charleskorn.kaml:kaml:0.49.0"
+
+commons-codec = "commons-codec:commons-codec:1.15"
+
+arrow-core = { group = "io.arrow-kt", name = "arrow-core", version.ref = "arrow" }
+
+ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
+
+ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" }
+
+ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
+
+ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
+
+ktor-server-tests-jvm = { group = "io.ktor", name = "ktor-server-tests-jvm", version.ref = "ktor" }
+
+commons-text = "org.apache.commons:commons-text:1.10.0"
+
+kotlin-test-junit = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit", version.ref = "kotlin" }
+
+kotlinx-cli = { group = "org.jetbrains.kotlinx", name = "kotlinx-cli", version.ref = "kotlinx-cli" }
+
+kotlinx-serialization-hocon = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-hocon", version.ref = "kotlinx-serialization" }
+
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
+
+kotlin-scripting-compiler-embeddable = { group = "org.jetbrains.kotlin", name = "kotlin-scripting-compiler-embeddable", version.ref = "kotlin" }
+
+kotlin-serialization = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" }
+
+ktlint = "com.pinterest:ktlint:0.43.2"
+## ⬆ :0.44.0"
+## ⬆ :0.45.0"
+## ⬆ :0.45.1"
+## ⬆ :0.45.2"
+## ⬆ :0.46.0"
+## ⬆ :0.46.1"
+## ⬆ :0.47.0"
+## ⬆ :0.47.1"
+
+arrow-fx-coroutines = { group = "io.arrow-kt", name = "arrow-fx-coroutines", version.ref = "arrow" }
+
+arrow-fx-stm = { group = "io.arrow-kt", name = "arrow-fx-stm", version.ref = "arrow" }
+
+junit-platform-native = "org.graalvm.buildtools:junit-platform-native:0.9.14"
+
+ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7454180
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ae04661
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/resources/config-sample.yaml b/resources/config-sample.yaml
deleted file mode 100644
index 108c4fe..0000000
--- a/resources/config-sample.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-http:
- path: "/"
- port: 8080
-
-runtime:
- workers: 2
- output: stdout
-
-projects: {}
- # myproject:
- # ref: "refs/heads/gh-pages"
- # directory: "/var/www/domain.com"
- # secret: "xxxxxxxxxxxxxxxxxxxx"
- # command: "git pull"
diff --git a/resources/config.yaml b/resources/config.yaml
deleted file mode 100644
index 90bd01a..0000000
--- a/resources/config.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-http:
- path: "/"
- port: 8080
-
-runtime:
- workers: 2
- output: stdout
-
-projects:
- myproject:
- ref: "refs/heads/gh-pages"
- directory: "/var/www/domain.com"
- secret: "xxxxxxxxxxxxxxxxxxxx"
- command: "git pull"
diff --git a/resources/github-webhook-listener.service b/resources/github-webhook-listener.service
deleted file mode 100644
index 741692d..0000000
--- a/resources/github-webhook-listener.service
+++ /dev/null
@@ -1,25 +0,0 @@
-[Unit]
-Description=GitHub Webhook Listener
-Requires=network.target
-
-[Service]
-Type=simple
-WorkingDirectory=/opt/github-webhook-listener/
-EnvironmentFile=
-ExecStart=/opt/github-webhook-listener/bin/github-webhook-listener -c /etc/github-webhook-listener/config.yaml
-ExecReload=/bin/kill -HUP $MAINPID
-StandardOutput=syslog
-StandardError=syslog
-Restart=always
-RestartSec=60
-SuccessExitStatus=
-TimeoutStopSec=5
-User=synchronize
-ExecStartPre=/bin/mkdir -p /run/github-webhook-listener
-ExecStartPre=/bin/chown synchronize:synchronize /run/github-webhook-listener
-ExecStartPre=/bin/chmod 755 /run/github-webhook-listener
-PermissionsStartOnly=true
-LimitNOFILE=1024
-
-[Install]
-WantedBy=multi-user.target
\ No newline at end of file
diff --git a/scripts/TestRequest.kt b/scripts/TestRequest.kt
new file mode 100755
index 0000000..722e255
--- /dev/null
+++ b/scripts/TestRequest.kt
@@ -0,0 +1,41 @@
+///usr/bin/env jbang "$0" "$@" ; exit $?
+//JAVA 17+
+//KOTLIN 1.7.20
+//DEPS org.apache.commons:commons-text:1.9
+//DEPS commons-codec:commons-codec:1.15
+//DEPS org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4
+//DEPS io.ktor:ktor-client-core-jvm:2.1.2
+//DEPS io.ktor:ktor-client-cio-jvm:2.1.2
+
+import kotlinx.coroutines.*
+import io.ktor.client.*
+import io.ktor.client.engine.cio.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import org.apache.commons.codec.digest.HmacAlgorithms
+import org.apache.commons.codec.digest.HmacUtils
+
+fun main() = runBlocking {
+ val signKey = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
+ val bodyText = """
+ {
+ "action": "push",
+ "ref": "refs/heads/gh-pages"
+ }
+ """.trimIndent()
+
+ val client = HttpClient(CIO)
+ val response =
+ client.post("http://localhost:8080/myproject") {
+ headers {
+ append(HttpHeaders.ContentType, "application/json")
+ append(
+ "X-Hub-Signature-256",
+ "sha256=" + HmacUtils(HmacAlgorithms.HMAC_SHA_256, signKey).hmacHex(bodyText)
+ )
+ }
+ setBody(bodyText)
+ }
+ println("HTTP ${response.status}: ${response.bodyAsText()}")
+}
diff --git a/scripts/java-exec b/scripts/java-exec
new file mode 100755
index 0000000..3607755
--- /dev/null
+++ b/scripts/java-exec
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+exec java \
+ -XX:+UseShenandoahGC \
+ -XX:+UnlockExperimentalVMOptions \
+ -XX:ShenandoahUncommitDelay=1000 \
+ -XX:ShenandoahGuaranteedGCInterval=10000 \
+ "$@"
diff --git a/scripts/new-version b/scripts/new-version.sh
similarity index 100%
rename from scripts/new-version
rename to scripts/new-version.sh
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..73c2a75
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,13 @@
+rootProject.name = "github-webhook-listener"
+
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ // See https://jmfayard.github.io/refreshVersions
+ id("de.fayard.refreshVersions") version "0.50.2"
+}
diff --git a/src/AppConfig.hs b/src/AppConfig.hs
deleted file mode 100644
index 4f13f72..0000000
--- a/src/AppConfig.hs
+++ /dev/null
@@ -1,72 +0,0 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE StrictData #-}
-
-module AppConfig (
- All (..),
- Http (..),
- Project(..),
- Runtime(..),
- readAppConfig) where
-
-import Data.Map (Map)
-import Data.Text
-import Data.Yaml (FromJSON, decodeFileThrow)
-import GHC.Generics
-import System.Directory (makeAbsolute)
-
--- | The complete format of the configuration file
-data All = All
- {
- http :: Http,
- runtime :: Runtime,
- projects :: Map Text Project
- } deriving (Generic, Show, Eq)
-
-instance FromJSON All
-
-{-|
- Configuration for where the web server will listen for requests
- (e.g. HTTP path and port)
--}
-data Http = Http
- {
- path :: Text -- ^ HTTP path prefix, to attach to all routes (e.g. "/" or "/api/")
- , port :: Integer -- ^ HTTP port to listen on (e.g. 8080)
- } deriving (Generic, Show, Eq)
-
-instance FromJSON Http
-
--- | Project configuration.
-data Project = Project
- {
- action :: Maybe Text -- ^ Identifies the GitHub event, none for "push"
- , ref :: Maybe Text -- ^ Git ref that we are expecting
- , directory :: Text -- ^ working local directory (e.g. `/var/www/website.com`)
- , command :: Text -- ^ shell command to execute
- , secret :: Maybe Text -- ^ key used to sign the request
- } deriving (Generic, Show, Eq)
-
-instance FromJSON Project
-
-{-|
- Configures misc runtime properties
--}
-data Runtime = Runtime
- {
- workers :: Int -- ^ the number of workers to process requests in parallel
- , output :: FilePath -- ^ where to send the logs to; accepts: stdout, stderr
- } deriving (Generic, Show, Eq)
-
-instance FromJSON Runtime
-
-{-|
- Given a file path, parses its contents into an 'All'
- configuration object.
-
- Can throw if the file isn't valid.
--}
-readAppConfig :: FilePath -> IO All
-readAppConfig filePath = do
- absolute <- makeAbsolute filePath
- decodeFileThrow absolute
diff --git a/src/CmdLine.hs b/src/CmdLine.hs
deleted file mode 100644
index 3218227..0000000
--- a/src/CmdLine.hs
+++ /dev/null
@@ -1,54 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE StrictData #-}
-
-module CmdLine (
- AppArgs (..),
- getCmdLineArgs) where
-
-import Control.Exception (Exception)
-import Control.Exception.Base (throwIO)
-import Options.Applicative
-import System.Directory (doesFileExist)
-
--- |Models command line arguments
-newtype AppArgs = AppArgs
- {
- configPath :: FilePath
- } deriving (Show)
-
--- |Thrown in case the given `configFile` does not exist
-newtype ConfigFileDoesNotExistsException =
- ConfigFileDoesNotExistsException String
- deriving Show
-
-instance Exception ConfigFileDoesNotExistsException
-
-appArgsParser :: Parser AppArgs
-appArgsParser = AppArgs
- <$> strOption
- ( long "config-path"
- <> short 'c'
- <> metavar "CONFIG_PATH"
- <> help "Path to the configuration file"
- )
-
-opts :: ParserInfo AppArgs
-opts = info (appArgsParser <**> helper)
- ( fullDesc
- <> progDesc "Starts the web server as configured via the indicated config file"
- <> header "github-webhook-listener - a web server that responds to GitHub's Webhooks" )
-
-{-|
- Parse command line arguments.
-
- Throws `ConfigFileDoesNotExistsException` in case the given
- `configFile` does not exist.
--}
-getCmdLineArgs :: IO AppArgs
-getCmdLineArgs = do
- config <- execParser opts
- hasFile <- doesFileExist (configPath config)
- if hasFile then
- return config
- else
- throwIO (ConfigFileDoesNotExistsException (configPath config))
diff --git a/src/Command.hs b/src/Command.hs
deleted file mode 100644
index 7cae7a9..0000000
--- a/src/Command.hs
+++ /dev/null
@@ -1,95 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE StrictData #-}
-
-module Command (startWorkers) where
-
-import AppConfig (Project(..))
-import Control.Concurrent (ThreadId, forkIO)
-import Control.Concurrent.Chan (Chan)
-import Control.Exception (catch)
-import Data.String (fromString)
-import Shelly (shelly, liftIO, bash_, chdir)
-import System.FilePath (joinPath)
-import Text.Regex (splitRegex, mkRegex)
-import Logger (LogHandle)
-
-import qualified AppConfig as AC
-import qualified Control.Concurrent.Chan as Chan
-import qualified Data.Text as T
-import qualified Logger
-import qualified System.FileLock as FL
-
-{-| Starts background workers that consume from the message channel and
-execute commands.
--}
--- startWorkers
--- :: Int -- ^ number of workers to start
--- -> Chan Project -- ^ the channel used for messaging
--- -> LogHandle -- ^ used for logging
--- -> IO ()
-startWorkers
- :: Int
- -> Chan Project
- -> LogHandle
- -> IO [ThreadId]
-startWorkers n chan h =
- sequence workers
- where
- consumer i = do
- Logger.logInfo h "Command" $ "Starting worker " <> (T.pack . show $ i)
- consumeFromChan chan h
- workers =
- fmap (forkIO . consumer) (enumFromTo 1 n)
-
-consumeFromChan
- :: Chan Project
- -> LogHandle
- -> IO ()
-consumeFromChan chan h =
- do
- command <- Chan.readChan chan
- executeShellCommand command h `catch` Logger.logCaughtError h "Command"
- consumeFromChan chan h -- continue
-
-{-|
- Executes the command for the given 'Project'.
-
- These steps are involved:
-
- * acquires a file lock (".webhook" in the project's directory)
- * switches to the project's directory
- * executes the command via "bash"
-
- If the program terminate in error, then this function will throw
- an exception.
--}
-executeShellCommand
- :: Project
- -> LogHandle
- -> IO ()
-executeShellCommand project h =
- withLock $
- unsafeExecuteShellCommand project h
- where
- lockFile = joinPath [T.unpack (AC.directory project), ".webhook"]
- withLock f = FL.withFileLock lockFile FL.Exclusive (const f)
-
-{-|
- Internal API: executes the shell command without a file lock.
--}
-unsafeExecuteShellCommand
- :: Project
- -> LogHandle
- -> IO ()
-unsafeExecuteShellCommand Project{..} h = shelly $ do
- liftIO $ Logger.logInfo h "Command" $ "Executing: chdir " <> directory
- chdir (fromString . T.unpack $ directory) $
- case commandAndArgs of
- x : xs -> do
- liftIO $ Logger.logInfo h "Command" $ "Executing: " <> command
- bash_ (fromString x) (fmap T.pack xs)
- [] ->
- liftIO $ Logger.logInfo h "Command" "No shell command to execute"
- where
- commandAndArgs = splitRegex (mkRegex "[ \t]+") (T.unpack command)
diff --git a/src/Controller.hs b/src/Controller.hs
deleted file mode 100644
index b9c4737..0000000
--- a/src/Controller.hs
+++ /dev/null
@@ -1,76 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE StrictData #-}
-
-module Controller (ping, processKey) where
-
-import Control.Concurrent.Chan (Chan, writeChan)
-import Control.Monad.IO.Class (liftIO)
-import Data.Map.Strict (Map)
-import Data.String (fromString)
-import Data.Text (Text)
-import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8)
-import Logger (LogHandle)
-import Network.HTTP.Types (status200, status404, status204, status403)
-import Web.Scotty
-
-import qualified AppConfig as Cfg
-import qualified Data.ByteString as BS
-import qualified Data.ByteString.Lazy as BSL
-import qualified Data.Map.Strict as Map
-import qualified Data.Text as DT
-import qualified Data.Text.Encoding as DTE
-import qualified Data.Text.Lazy as DTL
-import qualified Logger
-import qualified Payload as P
-
-ping :: DT.Text -> ScottyM ()
-ping path =
- get (fromString $ DT.unpack path) $
- html "Pong!"
-
-processKey :: DT.Text -> Map Text Cfg.Project -> Chan Cfg.Project -> LogHandle -> ScottyM ()
-processKey path projects chan h =
- post (fromString route) $ do
- key <- param "key"
- liftIO $ Logger.logInfo h "Controller" $ "POST " <> path <> key
- case Map.lookup key projects of
- Nothing -> send404 key
- Just project -> process key project
- where
- route = DT.unpack (path <> ":key")
-
- process key project = do
- b <- body
- hubSig <- getHeader "X-Hub-Signature"
- let bodyText = DTL.toStrict $ decodeUtf8 b
- let payload = P.parsePayload bodyText
- let action = payload >>= P.action
- let ref = payload >>= P.ref
-
- case P.validatePayload project payload hubSig bodyText of
- P.Execute -> do
- liftIO $ Logger.logInfo h "Controller" $ "Executing shell command for key: " <> key
- liftIO $ writeChan chan project
- status status200
- text "Ok"
- P.SkipExecution -> do
- liftIO $ Logger.logInfo h "Controller" $
- "Nothing to do for key: " <> key <> ", action: "
- <> P.actionLog action <> " and ref: "
- <> P.refLog ref
- status status204
- P.FailedAuthentication -> do
- liftIO $ Logger.logWarn h "Controller" $ "Validation failed for signature: " <> (DT.pack . show $ hubSig)
- status status403
- text "403 Forbidden"
-
- send404 key = do
- liftIO $ Logger.logWarn h "Controller" $ "404 Not Found: " <> key
- status status404
- text "404 Not Found"
-
- getHeader name =
- fmap (DTE.decodeUtf8 . lazyToStrict . encodeUtf8) <$>
- header name
- where
- lazyToStrict = BS.concat . BSL.toChunks
diff --git a/src/Logger.hs b/src/Logger.hs
deleted file mode 100644
index b765516..0000000
--- a/src/Logger.hs
+++ /dev/null
@@ -1,53 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE StrictData #-}
-
-module Logger (
- LogHandle
- , Severity(..)
- , logToHandle
- , logDebug
- , logInfo
- , logWarn
- , logError
- , logCaughtError) where
-
-import Data.Text as T
-import Data.Time.Clock
-import Data.Time.Format
-import Text.Printf (printf)
-import System.IO (Handle, hPutStrLn, stdout, hFlush)
-
--- | Just an alias for documentation purposes
-type LogHandle = Handle
-
-data Severity =
- DEBUG | INFO | WARN | ERROR
- deriving Show
-
-now :: IO Text
-now = do
- ts <- getCurrentTime
- let str = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S%z" ts
- return (pack str)
-
-logToHandle :: LogHandle -> Severity -> Text -> Text -> IO ()
-logToHandle handle severity context message = do
- nowStr <- now
- hPutStrLn handle (printf "[%s] [%s] [%s] %s" nowStr (show severity) context message)
- hFlush handle
-
-logDebug :: Text -> Text -> IO ()
-logDebug = logToHandle stdout DEBUG
-
-logInfo :: LogHandle -> Text -> Text -> IO ()
-logInfo handle = logToHandle handle INFO
-
-logWarn :: LogHandle -> Text -> Text -> IO ()
-logWarn handle = logToHandle handle WARN
-
-logError :: LogHandle -> Text -> Text -> IO ()
-logError handle = logToHandle handle ERROR
-
-logCaughtError :: LogHandle -> Text -> IOError -> IO ()
-logCaughtError handle ctx err =
- logToHandle handle ERROR ctx (T.pack (show err))
diff --git a/src/Payload.hs b/src/Payload.hs
deleted file mode 100644
index a9cc4f1..0000000
--- a/src/Payload.hs
+++ /dev/null
@@ -1,116 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE StrictData #-}
-
-module Payload (
- Payload(..),
- PAction(..),
- PRef(..),
- ValidateResult(..),
- parsePayload,
- validatePayload,
- actionLog,
- refLog) where
-
-import AppConfig (Project)
-import Control.Monad (mfilter)
-import Data.Aeson (FromJSON, decode)
-import Data.Digest.Pure.SHA (showDigest, hmacSha1)
-import Data.Text (Text)
-import Data.Text.Lazy (fromStrict)
-import GHC.Generics
-
-import qualified AppConfig as C
-import qualified Data.Text as T
-import qualified Data.Text.Lazy.Encoding as DTLE
-
-{-|
- Request received via GitHub's Webhooks.
-
- See:
--}
-data Payload = Payload
- {
- action :: Maybe PAction,
- ref :: Maybe PRef
- } deriving (Show, Generic)
-
-instance FromJSON Payload
-
-{-|
- GitHub repository action being performed.
-
- E.g. "opened". N.B. the "push" event doesn't have an "action"
- signaled in the payload.
--}
-newtype PAction = PAction Text
- deriving (Eq, Show, Generic)
-
-instance FromJSON PAction
-
--- | Unpacks a "Maybe" action for logging purposes.
-actionLog :: Maybe PAction -> Text
-actionLog Nothing = ""
-actionLog (Just (PAction a)) = a
-
-{-|
- Git ref, used to identify the branch or tag being acted upon.
- E.g. "refs/heads/gh-pages"
--}
-newtype PRef = PRef Text
- deriving (Eq, Show, Generic)
-
-instance FromJSON PRef
-
--- | Unpacks a "Maybe" ref for logging purposes.
-refLog :: Maybe PRef -> Text
-refLog Nothing = ""
-refLog (Just (PRef r)) = r
-
-{-|
- Validation result indicating what to do next.
-
- Used by the controller.
--}
-data ValidateResult =
- Execute -- ^ Command can be executed
- | SkipExecution -- ^ Failed conditions (e.g. ref / action)
- | FailedAuthentication -- ^ Failed authentication (e.g. signature)
- deriving (Eq, Show)
-
--- | For parsing a text into a 'Payload'
-parsePayload :: Text -> Maybe Payload
-parsePayload text =
- decode (DTLE.encodeUtf8 (fromStrict text))
-
-validatePayload :: Project -> Maybe Payload -> Maybe Text -> Text -> ValidateResult
-validatePayload _ Nothing _ _ = SkipExecution
-validatePayload project (Just p) sigHead body
- | not (isAuthenticated (C.secret project) sigHead body) =
- FailedAuthentication
- | action p /= expectedAction || ref p /= expectedRef =
- SkipExecution
- | otherwise =
- Execute
- where
- expectedAction =
- PAction <$> mfilter (/= "push") (C.action project)
- expectedRef =
- PRef <$> C.ref project
-
-isAuthenticated :: Maybe Text -> Maybe Text -> Text -> Bool
-isAuthenticated key sigHead body =
- case key of
- Nothing -> True
- Just k ->
- case sigHead of
- Just sig ->
- let (header, rest) = T.splitAt 5 sig in
- header == "sha1=" && rest == T.pack digest
- _ ->
- False
- where
- digest =
- let digestKey = DTLE.encodeUtf8 . fromStrict $ k in
- let digestBody = DTLE.encodeUtf8 . fromStrict $ body in
- showDigest (hmacSha1 digestKey digestBody)
diff --git a/src/Server.hs b/src/Server.hs
deleted file mode 100644
index 5088bfb..0000000
--- a/src/Server.hs
+++ /dev/null
@@ -1,57 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE StrictData #-}
-
-module Server (run) where
-
-import Control.Concurrent (killThread)
-import Control.Exception (bracket, uninterruptibleMask_)
-import Control.Concurrent.Chan (Chan)
-import Controller (ping, processKey)
-import Logger (LogHandle)
-import Web.Scotty (scotty)
-
-import qualified AppConfig as Cfg
-import qualified Control.Concurrent.Chan as Chan
-import qualified Command
-import qualified Logger
-import qualified Data.Text as T
-
-
-startServer :: Cfg.All -> Chan Cfg.Project -> LogHandle -> IO ()
-startServer config chan h =
- scotty port $
- ping path >>
- processKey path (Cfg.projects config) chan h
- where
- port = fromInteger (toInteger (Cfg.port . Cfg.http $ config))
- path = Cfg.path . Cfg.http $ config
-
-
-run :: Cfg.All -> LogHandle -> IO ()
-run appConfig h = do
- Logger.logInfo h "Main" "Starting server ..."
- -- Message queue used for delaying requests
- chan <- Chan.newChan
- -- Ensures that resources terminate safely
- safeBracket
- -- Workers processing requests asynchronously
- (startWorkers chan)
- -- Terminates workers on server shutdown
- (mapM_ killThreadAndLog)
- -- Starts server for receiving HTTP requests
- (const (startServer appConfig chan h))
- where
- startWorkers chan =
- Command.startWorkers (Cfg.workers . Cfg.runtime $ appConfig) chan h
- killThreadAndLog tid = do
- Logger.logInfo h "Command" $ "Killing worker (" <> (T.pack . show $ tid) <> ")"
- killThread tid
-
--- | Version of bracket that makes the finalizer uninterruptible
-safeBracket
- :: IO a -- ^ acquisition
- -> (a -> IO b) -- ^ release
- -> (a -> IO c) -- ^ use
- -> IO c
-safeBracket ini fin =
- bracket (uninterruptibleMask_ ini) (uninterruptibleMask_ . fin)
diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..46929d2
--- /dev/null
+++ b/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,34 @@
+# To build:
+#
+# docker build -f ./src/main/docker/Dockerfile.jvm -t github-webhook-listener-jvm .
+#
+# To run:
+#
+# docker run -p 8080:8080 github-webhook-listener-jvm
+#
+FROM --platform=linux/amd64 gradle:7-jdk17 AS build
+COPY --chown=gradle:gradle . /home/gradle/src
+RUN ls -alh /home/gradle/src
+WORKDIR /home/gradle/src
+RUN gradle buildFatJar --no-daemon
+
+##################################################
+
+FROM --platform=linux/amd64 alpine:latest
+RUN mkdir -p /opt/app
+RUN mkdir -p /opt/app/config
+RUN adduser -u 1001 -h /opt/app -s /bin/sh -D appuser
+WORKDIR /opt/app
+RUN chown -R appuser /opt/app && chmod -R "g+rwX" /opt/app && chown -R appuser:root /opt/app
+
+RUN apk add --no-cache openjdk17-jre-headless
+# RUN apk add --no-cache git curl jq
+
+COPY --from=build --chown=appuser:root /home/gradle/src/build/libs/github-webhook-listener-fat.jar /opt/app/github-webhook-listener-fat.jar
+COPY --from=build --chown=appuser:root /home/gradle/src/config/application-dummy.yaml /opt/app/config/config.yaml
+COPY --from=build --chown=appuser:root /home/gradle/src/scripts/java-exec /opt/app/java-exec
+
+EXPOSE 8080
+USER appuser
+
+CMD ["/opt/app/java-exec","-jar","/opt/app/github-webhook-listener-fat.jar","/opt/app/config/config.yaml"]
diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..3066dab
--- /dev/null
+++ b/src/main/docker/Dockerfile.native
@@ -0,0 +1,29 @@
+# To build:
+#
+# docker build -f ./src/main/docker/Dockerfile.native -t github-webhook-listener-native .
+#
+# To run:
+#
+# docker run -p 8080:8080 github-webhook-listener-native
+#
+FROM --platform=linux/amd64 ghcr.io/graalvm/native-image:22.2.0 AS build
+COPY --chown=root:root . /app/source
+WORKDIR /app/source
+RUN ./gradlew nativeCompile --no-daemon
+
+FROM --platform=linux/amd64 alpine:latest
+RUN mkdir -p /opt/app/config
+RUN adduser -u 1001 -h /opt/app -s /bin/sh -D appuser
+WORKDIR /opt/app
+RUN chown -R appuser /opt/app && chmod -R "g+rwX" /opt/app && chown -R appuser:root /opt/app
+
+RUN apk add --no-cache gcompat
+# RUN apk add --no-cache git curl jq
+
+COPY --from=build --chown=appuser:root /app/source/build/native/nativeCompile/github-webhook-listener /opt/app/github-webhook-listener
+COPY --from=build --chown=appuser:root /app/source/config/application-dummy.yaml /opt/app/config/config.yaml
+
+EXPOSE 8080
+USER appuser
+
+CMD ["/opt/app/github-webhook-listener", "/opt/app/config/config.yaml"]
diff --git a/src/main/docker/Dockerfile.native.old b/src/main/docker/Dockerfile.native.old
new file mode 100644
index 0000000..5d6f9c6
--- /dev/null
+++ b/src/main/docker/Dockerfile.native.old
@@ -0,0 +1,27 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./gradlew build -Dquarkus.package.type=native
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/rest-kotlin-quickstart .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/rest-kotlin-quickstart
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root build/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/src/main/kotlin/org/alexn/hook/AppConfig.kt b/src/main/kotlin/org/alexn/hook/AppConfig.kt
new file mode 100644
index 0000000..d9143a1
--- /dev/null
+++ b/src/main/kotlin/org/alexn/hook/AppConfig.kt
@@ -0,0 +1,69 @@
+package org.alexn.hook
+
+import com.charleskorn.kaml.Yaml
+import com.charleskorn.kaml.YamlConfiguration
+import com.typesafe.config.ConfigFactory
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.hocon.Hocon
+import java.io.File
+import kotlin.time.Duration
+
+@Serializable
+data class AppConfig(
+ val http: Http,
+ val projects: Map,
+) {
+ @Serializable
+ data class Http(
+ val port: Int,
+ val host: String? = null,
+ val path: String? = null,
+ ) {
+ val basePath: String
+ get() {
+ var bp = path ?: return ""
+ if (bp.endsWith("/")) bp = bp.dropLast(1)
+ return bp
+ }
+ }
+
+ @Serializable
+ data class Project(
+ val ref: String,
+ val directory: String,
+ val command: String,
+ val secret: String,
+ val action: String? = null,
+ val timeout: Duration? = null,
+ )
+
+ companion object {
+ @OptIn(ExperimentalSerializationApi::class)
+ fun parseHocon(string: String): AppConfig =
+ Hocon.decodeFromConfig(
+ serializer(),
+ ConfigFactory.parseString(string).resolve()
+ )
+
+ fun parseYaml(string: String): AppConfig =
+ yamlParser.decodeFromString(
+ serializer(),
+ string
+ )
+
+ fun loadFromFile(file: File): AppConfig {
+ val txt = file.readText()
+ return if (file.extension.matches("(?i)yaml|yml".toRegex()))
+ parseYaml(txt)
+ else
+ parseHocon(txt)
+ }
+
+ private val yamlParser = Yaml(
+ configuration = YamlConfiguration(
+ strictMode = false
+ )
+ )
+ }
+}
diff --git a/src/main/kotlin/org/alexn/hook/CommandTrigger.kt b/src/main/kotlin/org/alexn/hook/CommandTrigger.kt
new file mode 100644
index 0000000..2c7447b
--- /dev/null
+++ b/src/main/kotlin/org/alexn/hook/CommandTrigger.kt
@@ -0,0 +1,71 @@
+package org.alexn.hook
+
+import arrow.core.Either
+import arrow.core.left
+import arrow.core.right
+import arrow.fx.coroutines.Atomic
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.withTimeout
+import java.io.File
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * Handles the actual shell command execution, per project.
+ */
+class CommandTrigger private constructor(
+ private val projects: Map,
+ private val locks: Atomic