├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── README.md
├── build.sbt
├── maven-repository-settings.png
├── project
├── build.properties
└── plugins.sbt
└── src
└── main
├── resources
└── update
│ └── gitbucket-maven-repository_1.1.0.xml
├── scala
├── Plugin.scala
└── io
│ └── github
│ └── gitbucket
│ └── mavenrepository
│ ├── command
│ ├── AbstractCommand.scala
│ ├── LsCommand.scala
│ └── MkdirCommand.scala
│ ├── controller
│ └── MavenRepositoryController.scala
│ ├── model
│ ├── Registry.scala
│ └── RegistryProfile.scala
│ ├── package.scala
│ └── service
│ └── MavenRepositoryService.scala
└── twirl
└── gitbucket
└── mavenrepository
├── files.scala.html
├── form.scala.html
└── settings.scala.html
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | java: [8, 11]
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Cache
14 | uses: actions/cache@v2
15 | env:
16 | cache-name: cache-sbt-libs
17 | with:
18 | path: |
19 | ~/.ivy2/cache
20 | ~/.sbt
21 | ~/.coursier
22 | key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
23 | - name: Set up JDK
24 | uses: actions/setup-java@v1
25 | with:
26 | java-version: ${{ matrix.java }}
27 | - name: Run tests
28 | run: |
29 | git clone https://github.com/gitbucket/gitbucket.git
30 | cd gitbucket
31 | sbt publishLocal
32 | cd ../
33 | sbt test
34 | - name: Assembly
35 | run: sbt assembly
36 | - name: Upload artifacts
37 | uses: actions/upload-artifact@v2
38 | with:
39 | name: gitbucket-gist-plugin-java${{ matrix.java }}-${{ github.sha }}
40 | path: ./target/scala-2.13/*.jar
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | dist/*
6 | target/
7 | lib_managed/
8 | src_managed/
9 | project/boot/
10 | project/plugins/project/
11 |
12 | # Scala-IDE specific
13 | .scala_dependencies
14 | .classpath
15 | .project
16 | .cache
17 | .settings
18 |
19 | # IntelliJ specific
20 | .idea/
21 | .idea_modules/
22 |
23 | # Ensime
24 | .ensime
25 | .ensime_cache/
26 |
27 | # Metals
28 | .bloop/
29 | .metals/
30 | .vscode/
31 | **/metals.sbt
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gitbucket-maven-repository-plugin [](https://github.com/takezoe/gitbucket-maven-repository-plugin/actions?query=workflow%3Abuild+branch%3Amaster)
2 | ========
3 | A GitBucket plugin that provides Maven repository hosting on GitBucket.
4 |
5 | ## Features
6 |
7 | In default, following Maven repositories become available by installing this plugin to GitBucket.
8 |
9 | - `http(s)://GITBUCKET_HOST/maven/releases`
10 | - `http(s)://GITBUCKET_HOST/maven/snapshots`
11 |
12 | You can deploy artifacts to these repositories via WebDAV with your GitBucket account.
13 |
14 | Also you can deploy via SSH (SCP) with public key authentication using keys registered in GitBucket. In this case, use following configurations to connect via SSH:
15 |
16 | - Host: Hostname of GitBucket
17 | - Port: SSH port configured in GitBucket system settings
18 | - Path: `/maven/releases` or `/maven/snapshots`
19 |
20 | It's possible to add more repositories and configure them at the administration console:
21 |
22 | 
23 |
24 | You can specify whether artifacts are overwritable for each repository. In addition, it's possible to make repository private. Private repositories require basic authentication by GitBucket account to access.
25 |
26 | ## Compatibility
27 |
28 | Plugin version | GitBucket version
29 | :--------------|:--------------------
30 | 1.8.x | 4.37.1 -
31 | 1.7.x | 4.36.x -
32 | 1.6.x | 4.35.x -
33 | 1.5.x | 4.32.x -
34 | 1.4.x | 4.30.x -
35 | 1.3.x - | 4.23.x -
36 | 1.1.x - 1.2.x | 4.21.x -
37 | 1.0.x | 4.19.x -
38 |
39 | ## Installation
40 |
41 | Download jar file from [the release page](https://github.com/takezoe/gitbucket-maven-repository-plugin/releases) and put into `GITBUCKET_HOME/plugins`.
42 |
43 | ## Build
44 |
45 | Run `sbt assembly` and copy generated `/target/scala-2.13/gitbucket-maven-repository-plugin-x.x.x.jar` to `~/.gitbucket/plugins/` (If the directory does not exist, create it by hand before copying the jar), or just run `sbt install`.
46 |
47 | ## Configuration
48 |
49 | ### sbt
50 |
51 | Resolvers:
52 |
53 | ```scala
54 | resolvers ++= Seq(
55 | "GitBucket Snapshots Repository" at "http://localhost:8080/maven/snapshots",
56 | "GitBucket Releases Repository" at "http://localhost:8080/maven/releases"
57 | )
58 |
59 | // If repository is private, you have to add authentication information
60 | credentials += Credentials("GitBucket Maven Repository", "localhost", "username", "password")
61 | ```
62 |
63 | Publish via WebDAV:
64 |
65 | ```scala
66 | publishTo := {
67 | val base = "http://localhost:8080/maven/"
68 | if (version.value.endsWith("SNAPSHOT")) Some("snapshots" at base + "snapshots")
69 | else Some("releases" at base + "releases")
70 | }
71 |
72 | credentials += Credentials("GitBucket Maven Repository", "localhost", "username", "password")
73 | ```
74 |
75 | Publish via SSH:
76 |
77 | ```scala
78 | publishTo := {
79 | val repoInfo =
80 | if (version.value.endsWith("SNAPSHOT")) ("snapshots" -> "/maven/snapshots")
81 | else ("releases" -> "/maven/releases")
82 | Some(Resolver.ssh(repoInfo._1, "localhost", 29418, repoInfo._2)
83 | as(System.getProperty("user.name"), (Path.userHome / ".ssh" / "id_rsa").asFile))
84 | }
85 | ```
86 |
87 | ### Maven
88 |
89 | Add distribution settings to your `pom.xml`:
90 |
91 | ```xml
92 |
93 | ...
94 |
95 |
96 | gitbucket-maven-repository-releases
97 | http://localhost:8080/maven/releases
98 |
99 |
100 | gitbucket-maven-repository-snapshots
101 | http://localhost:8080/maven/snapshots
102 |
103 |
104 | ...
105 |
106 | ```
107 |
108 | Also you need to add authentication settings in `~/.m2/settings.xml` (replace username and password with your GitBucket account's one):
109 |
110 | ```xml
111 |
112 | ...
113 |
114 |
115 | gitbucket-maven-repository-releases
116 | root
117 | root
118 |
119 |
120 | gitbucket-maven-repository-snapshots
121 | root
122 | root
123 |
124 |
125 | ...
126 |
127 | ```
128 |
129 | ### Gradle
130 |
131 | To publish your artifacts to a Maven repo hosted by **this** GitBucket plug-in, only the urls
132 | in your `uploadArchives.repositories` section of your `build.gradle` need to be changed (using your installation's URLs):
133 | ```groovy
134 | // ...
135 | def mvnUser = hasProperty("mvnUser") ? mvnUser : "no_user"
136 | def mvnPassword = hasProperty("mvnPassword") ? mvnPassword : "no_pwd"
137 | // ...
138 | uploadArchives {
139 | repositories {
140 | mavenDeployer {
141 | repository(url:"http://localhost:8080/maven/releases") {
142 | authentication(userName: mvnUser, password: mvnPassword)
143 | }
144 | snapshotRepository(url: "http://localhost:8080/maven/snapshots") {
145 | authentication(userName: mvnUser, password: mvnPassword)
146 | }
147 | }
148 | }
149 | }
150 | // ...
151 | ```
152 | where `mvnUser` and `mvnPassword` are set in your `~/.gradle/gradle.properties`
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | name := "gitbucket-maven-repository-plugin"
2 | organization := "io.github.gitbucket"
3 | version := "1.8.0"
4 | scalaVersion := "2.13.7"
5 | gitbucketVersion := "4.37.1"
6 | scalacOptions += "-deprecation"
7 | resolvers += Resolver.mavenLocal
8 | libraryDependencies ++= Seq(
9 | "org.apache.sshd" % "sshd-scp" % "2.8.0"
10 | )
11 |
12 | assembly / assemblyMergeStrategy := {
13 | case PathList("META-INF", xs @ _*) =>
14 | (xs map { _.toLowerCase }) match {
15 | case ("manifest.mf" :: Nil) => MergeStrategy.discard
16 | case _ => MergeStrategy.discard
17 | }
18 | case x => MergeStrategy.first
19 | }
--------------------------------------------------------------------------------
/maven-repository-settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takezoe/gitbucket-maven-repository-plugin/674e1c0382fbfbf37ef35492797822920be4b8df/maven-repository-settings.png
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version = 1.5.6
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("io.github.gitbucket" % "sbt-gitbucket-plugin" % "1.5.1")
2 |
--------------------------------------------------------------------------------
/src/main/resources/update/gitbucket-maven-repository_1.1.0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/scala/Plugin.scala:
--------------------------------------------------------------------------------
1 | import java.io.{File, IOException, OutputStream}
2 | import java.nio.file.{Files, OpenOption, Path}
3 | import java.nio.file.attribute.PosixFilePermission
4 |
5 | import gitbucket.core.controller.Context
6 | import gitbucket.core.plugin.Link
7 | import gitbucket.core.servlet.Database
8 | import gitbucket.core.model.Profile.profile.blockingApi._
9 | import io.github.gitbucket.mavenrepository._
10 | import io.github.gitbucket.mavenrepository.command.{LsCommand, MkdirCommand}
11 | import io.github.gitbucket.mavenrepository.controller.MavenRepositoryController
12 | import io.github.gitbucket.mavenrepository.service.MavenRepositoryService
13 | import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration}
14 | import io.github.gitbucket.solidbase.model.Version
15 | import org.apache.sshd.scp.common.helpers.DefaultScpFileOpener
16 | import org.apache.sshd.scp.server.ScpCommand
17 | import org.apache.sshd.common.session.Session
18 | import org.apache.sshd.server.channel.ChannelSession
19 |
20 | class Plugin extends gitbucket.core.plugin.Plugin with MavenRepositoryService {
21 | override val pluginId: String = "maven-repository"
22 | override val pluginName: String = "Maven Repository Plugin"
23 | override val description: String = "Host Maven repository on GitBucket."
24 | override val versions: List[Version] = List(
25 | new Version("1.0.0"),
26 | new Version("1.0.1"),
27 | new Version("1.1.0",
28 | new LiquibaseMigration("update/gitbucket-maven-repository_1.1.0.xml"),
29 | (moduleId: String, version: String, context: java.util.Map[String, AnyRef]) => {
30 | new File(s"${RegistryPath}/releases").mkdirs()
31 | new File(s"${RegistryPath}/snapshots").mkdirs()
32 | }
33 | ),
34 | new Version("1.2.0"),
35 | new Version("1.2.1"),
36 | new Version("1.3.0"),
37 | new Version("1.3.1"),
38 | new Version("1.3.2"),
39 | new Version("1.4.0"),
40 | new Version("1.5.0"),
41 | new Version("1.6.0"),
42 | new Version("1.7.0"),
43 | new Version("1.8.0")
44 | )
45 |
46 | override val sshCommandProviders = Seq({
47 | case command: String if checkCommand(command) => (session: ChannelSession) => {
48 | val index = command.indexOf('/')
49 | val path = command.substring(index + "/maven".length)
50 | val registryName = path.split("/")(1)
51 | val registry = Database() withTransaction { implicit session => getMavenRepository(registryName).get }
52 | val fullPath = s"${RegistryPath}/${path}"
53 |
54 | if(command.startsWith("scp")){
55 | new ScpCommand(
56 | session,
57 | s"scp -t -d ${fullPath}",
58 | null, // executorService
59 | 1024 * 128, // sendSize
60 | 1024 * 128, // receiveSize
61 | new DefaultScpFileOpener(){
62 | override def openWrite(session: Session, file: Path, size: Long, permissions: java.util.Set[PosixFilePermission], options: OpenOption*): OutputStream = {
63 | val fileName = file.getFileName.toString
64 | if(fileName == "maven-metadata.xml" || fileName.startsWith("maven-metadata.xml.")){
65 | // accept
66 | } else if(registry.overwrite == false && Files.exists(file)){
67 | throw new IOException("Rejected.")
68 | }
69 | super.openWrite(session, file, size, permissions, options: _*)
70 | }
71 | },
72 | null // eventListener
73 | )
74 | } else if(command.startsWith("mkdir")){
75 | new MkdirCommand(new File(fullPath))
76 | } else {
77 | new LsCommand(new File(fullPath))
78 | }
79 | }
80 | })
81 |
82 | /**
83 | * Check the existence of the library repository.
84 | */
85 | private def checkCommand(command: String): Boolean = {
86 | Database() withTransaction { implicit session =>
87 | getMavenRepositories().exists { registry =>
88 | command.matches(s"scp .* /maven/${registry.name}/.*") ||
89 | command.startsWith(s"ls /maven/${registry.name}") ||
90 | command.startsWith(s"mkdir /maven/${registry.name}")
91 | }
92 | }
93 | }
94 |
95 | private val controller = new MavenRepositoryController()
96 |
97 | override val controllers = Seq(
98 | "/maven/*" -> controller,
99 | "/admin/maven/*" -> controller
100 | )
101 |
102 | override val anonymousAccessiblePaths = Seq("/maven")
103 |
104 | override val systemSettingMenus: Seq[Context => Option[Link]] = Seq(
105 | _ => Some(Link("maven", "Maven repositories", "admin/maven", Some("package")))
106 | )
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/command/AbstractCommand.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.command
2 |
3 | import java.io.{InputStream, OutputStream}
4 |
5 | import org.apache.sshd.server.command.Command
6 | import org.apache.sshd.server.{Environment, ExitCallback}
7 | import org.apache.sshd.server.channel.ChannelSession
8 |
9 | abstract class AbstractCommand extends Command {
10 |
11 | protected val Success = 0
12 | protected val Failure = -1
13 |
14 | protected var in: InputStream = null
15 | protected var out: OutputStream = null
16 | protected var err: OutputStream = null
17 | protected var callback: ExitCallback = null
18 | override def setErrorStream(err: OutputStream): Unit = this.err = err
19 | override def setOutputStream(out: OutputStream): Unit = this.out = out
20 | override def setInputStream(in: InputStream): Unit = this.in = in
21 | override def setExitCallback(callback: ExitCallback): Unit = this.callback = callback
22 | override def start(session: ChannelSession, env: Environment): Unit = {
23 | val exitCode = execute()
24 | out.flush()
25 |
26 | in.close()
27 | out.close()
28 | err.close()
29 |
30 | callback.onExit(exitCode)
31 | }
32 | override def destroy(session: ChannelSession): Unit = {}
33 | protected def execute(): Int
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/command/LsCommand.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.command
2 |
3 | import java.io.File
4 |
5 | class LsCommand(dir: File) extends AbstractCommand {
6 | override protected def execute(): Int = {
7 | if(dir.exists && dir.isDirectory){
8 | val result = dir.listFiles.map(_.getName).mkString("\t")
9 | out.write(result.getBytes("UTF-8"))
10 | Success
11 | } else {
12 | Failure
13 | }
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/command/MkdirCommand.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.command
2 |
3 | import java.io.File
4 |
5 | class MkdirCommand(dir: File) extends AbstractCommand {
6 | override def execute(): Int = {
7 | dir.mkdirs()
8 | Success
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/controller/MavenRepositoryController.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.controller
2 |
3 | import java.io.{File, FileInputStream, FileOutputStream}
4 | import java.nio.file.{Files, Paths}
5 |
6 | import io.github.gitbucket.mavenrepository._
7 | import gitbucket.core.controller.ControllerBase
8 | import gitbucket.core.model.Account
9 | import gitbucket.core.service.AccountService
10 | import gitbucket.core.util.{AdminAuthenticator, AuthUtil, FileUtil}
11 | import gitbucket.core.util.Implicits._
12 | import io.github.gitbucket.mavenrepository.service.MavenRepositoryService
13 | import org.apache.commons.io.{FileUtils, IOUtils}
14 | import org.scalatra.forms._
15 | import org.scalatra.i18n.Messages
16 | import org.scalatra.{ActionResult, NotAcceptable, Ok}
17 | import scala.util.Using
18 | import org.scalatra.BadRequest
19 |
20 | class MavenRepositoryController extends ControllerBase with AccountService with MavenRepositoryService
21 | with AdminAuthenticator {
22 |
23 | case class RepositoryCreateForm(name: String, description: Option[String], overwrite: Boolean, isPrivate: Boolean)
24 | case class RepositoryEditForm(description: Option[String], overwrite: Boolean, isPrivate: Boolean)
25 |
26 | val repositoryCreateForm = mapping(
27 | "name" -> trim(label("Name", text(required, identifier, maxlength(100), unique))),
28 | "description" -> trim(label("Description", optional(text()))),
29 | "overwrite" -> trim(boolean()),
30 | "isPrivate" -> trim(boolean())
31 | )(RepositoryCreateForm.apply)
32 |
33 | val repositoryEditForm = mapping(
34 | "description" -> trim(label("Description", optional(text()))),
35 | "overwrite" -> trim(boolean()),
36 | "isPrivate" -> trim(boolean())
37 | )(RepositoryEditForm.apply)
38 |
39 |
40 | get("/admin/maven")(adminOnly {
41 | gitbucket.mavenrepository.html.settings(getMavenRepositories())
42 | })
43 |
44 | get("/admin/maven/_new")(adminOnly {
45 | gitbucket.mavenrepository.html.form(None)
46 | })
47 |
48 | post("/admin/maven/_new", repositoryCreateForm)(adminOnly { form =>
49 | createRegistry(form.name, form.description, form.overwrite, form.isPrivate)
50 | redirect("/admin/maven")
51 | })
52 |
53 | get("/admin/maven/:name/_edit")(adminOnly {
54 | gitbucket.mavenrepository.html.form(getMavenRepository(params("name")))
55 | })
56 |
57 | post("/admin/maven/:name/_edit", repositoryEditForm)(adminOnly { form =>
58 | updateRegistry(params("name"), form.description, form.overwrite, form.isPrivate)
59 | redirect("/admin/maven")
60 | })
61 |
62 | post("/admin/maven/:name/_delete")(adminOnly {
63 | deleteRegistry(params("name"))
64 | redirect("/admin/maven")
65 | })
66 |
67 | private def basicAuthentication(): Either[ActionResult, Account] = {
68 | request.header("Authorization").flatMap {
69 | case auth if auth.startsWith("Basic ") => {
70 | val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
71 | authenticate(context.settings, username, password)
72 | }
73 | case _ => None
74 | }.toRight {
75 | response.setHeader("WWW-Authenticate", "Basic realm=\"GitBucket Maven Repository\"")
76 | org.scalatra.Unauthorized()
77 | }
78 | }
79 |
80 | post("/admin/maven/:name/_deletefiles")(adminOnly {
81 | val name = params("name")
82 | val path = validatePath(params("path"))
83 | val files = multiParams("files")
84 |
85 | files.foreach { file =>
86 | val fullPath = if (path.nonEmpty) {
87 | s"${RegistryPath}/${name}/${path}/${file}"
88 | } else {
89 | s"${RegistryPath}/${name}/${file}"
90 | }
91 | val f = new File(fullPath)
92 | FileUtils.deleteQuietly(f)
93 | }
94 | if (path.nonEmpty) {
95 | redirect(s"/maven/${name}/${path}/")
96 | } else {
97 | redirect(s"/maven/${name}/")
98 | }
99 | })
100 |
101 | get("/maven/:name"){
102 | download(params("name"), "")
103 | }
104 |
105 | get("/maven/:name/*"){
106 | val path = validatePath(multiParams("splat").head)
107 | download(params("name"), path)
108 | }
109 |
110 | private def download(name: String, path: String) = {
111 | val result = for {
112 | // Find registry
113 | registry <- getMavenRepository(name).toRight { NotFound() }
114 | // Basic authentication
115 | _ <- if(registry.isPrivate){ basicAuthentication().map(x => Some(x)) } else Right(None)
116 | //path = multiParams("splat").head
117 | file = new File(s"${RegistryPath}/${name}/${path}")
118 | } yield {
119 | file match {
120 | // Download the file
121 | case f if f.exists && f.isFile =>
122 | contentType = FileUtil.getMimeType(path)
123 | response.setContentLength(file.length.toInt)
124 | Using.resource(new FileInputStream(file)){ in =>
125 | IOUtils.copy(in, response.getOutputStream)
126 | }
127 |
128 | // Render the directory index
129 | case f if f.exists && f.isDirectory =>
130 | val files = file.listFiles.toSeq.sortWith { (file1, file2) =>
131 | (file1.isDirectory, file2.isDirectory) match {
132 | case (true , false) => true
133 | case (false, true ) => false
134 | case _ => file1.getName.compareTo(file2.getName) < 0
135 | }
136 | }
137 |
138 | gitbucket.mavenrepository.html.files(name, path, files)
139 |
140 | // Otherwise
141 | case _ => NotFound()
142 | }
143 | }
144 |
145 | result.fold(identity, identity)
146 | }
147 |
148 | put("/maven/:name/*"){
149 | val name = params("name")
150 | val path = validatePath(multiParams("splat").head)
151 |
152 | val result = for {
153 | // Find registry
154 | registry <- getMavenRepository(name).toRight { NotFound() }
155 | // Basic authentication
156 | _ <- if(registry.isPrivate){ basicAuthentication().map(x => Some(x)) } else Right(None)
157 | // Overwrite check
158 | file = new File(s"${RegistryPath}/${name}/${path}")
159 | _ <- if(file.getName == "maven-metadata.xml" || file.getName.startsWith("maven-metadata.xml.")){
160 | Right(())
161 | } else if(!registry.overwrite && file.exists){
162 | Left(NotAcceptable())
163 | } else {
164 | Right(())
165 | }
166 | } yield {
167 | val parent = file.getParentFile
168 | if(!parent.exists){
169 | parent.mkdirs()
170 | }
171 | Using.resource(new FileOutputStream(file)){ out =>
172 | IOUtils.copy(request.getInputStream, out)
173 | }
174 | Ok()
175 | }
176 |
177 | result.fold(identity, identity)
178 | }
179 |
180 | // authentication required
181 | // delete artifacts, only if the registry is overwritable
182 | delete("/maven/:name/*") {
183 | val name = params("name")
184 | val path = validatePath(multiParams("splat").head)
185 |
186 | val result = for {
187 | registry <- getMavenRepository(name).toRight(NotFound())
188 | _ <- basicAuthentication()
189 | path = multiParams("splat").head
190 | file = Paths.get(RegistryPath, name, path)
191 | repoBase = Paths.get(RegistryPath, registry.name)
192 | _ <- if (!Files.exists(file) || !registry.overwrite) Left(NotAcceptable()) else Right(())
193 | } yield {
194 | if (Files.isSameFile(repoBase, file)) {
195 | // clean up repository
196 | FileUtils.cleanDirectory(file.toFile)
197 | } else {
198 | // remove file and remove the directory if it's empty
199 | FileUtils.deleteDirectory(file.toFile)
200 | val parent = file.getParent
201 | if (!Files.isSameFile(repoBase, parent)) {
202 | FileUtil.deleteDirectoryIfEmpty(parent.toFile)
203 | }
204 | }
205 | Ok()
206 | }
207 |
208 | result.fold(identity, identity)
209 | }
210 |
211 | private def unique: Constraint = new Constraint(){
212 | override def validate(name: String, value: String, messages: Messages): Option[String] = {
213 | getMavenRepository(value).map { _ => "Repository already exist." }
214 | }
215 | }
216 |
217 | private def validatePath(path: String): String = {
218 | if (path != null && path.contains("..")) {
219 | halt(BadRequest())
220 | }
221 | path
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/model/Registry.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.model
2 |
3 | trait RegistryComponent { self: gitbucket.core.model.Profile =>
4 | import profile.api._
5 | import self._
6 |
7 | lazy val Registries = TableQuery[Registries]
8 |
9 | class Registries(tag: Tag) extends Table[Registry](tag, "REGISTRY"){
10 | val name = column[String]("NAME")
11 | val description = column[String]("DESCRIPTION")
12 | val overwrite = column[Boolean]("OVERWRITE")
13 | val isPrivate = column[Boolean]("PRIVATE")
14 | def * = (name, description.?, overwrite, isPrivate) <> (Registry.tupled, Registry.unapply)
15 | }
16 |
17 | }
18 |
19 | case class Registry(
20 | name: String,
21 | description: Option[String],
22 | overwrite: Boolean,
23 | isPrivate: Boolean
24 | )
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/model/RegistryProfile.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.model
2 |
3 | import gitbucket.core.model._
4 |
5 | object Profile extends CoreProfile
6 | with RegistryComponent
7 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/package.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket
2 |
3 | import gitbucket.core.util.Directory
4 |
5 | package object mavenrepository {
6 |
7 | val RegistryPath = s"${Directory.GitBucketHome}/maven"
8 |
9 | }
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/scala/io/github/gitbucket/mavenrepository/service/MavenRepositoryService.scala:
--------------------------------------------------------------------------------
1 | package io.github.gitbucket.mavenrepository.service
2 |
3 | import java.io.File
4 |
5 | import io.github.gitbucket.mavenrepository.RegistryPath
6 | import io.github.gitbucket.mavenrepository.model.Registry
7 | import io.github.gitbucket.mavenrepository.model.Profile._
8 | import io.github.gitbucket.mavenrepository.model.Profile.profile.blockingApi._
9 | import org.apache.commons.io.FileUtils
10 | import gitbucket.core.util.SyntaxSugars._
11 |
12 | trait MavenRepositoryService {
13 |
14 | def getMavenRepository(name: String)(implicit s: Session): Option[Registry] = {
15 | Registries.filter(_.name === name.bind).firstOption
16 | }
17 |
18 | def getMavenRepositories()(implicit s: Session): Seq[Registry] = {
19 | Registries.sortBy(_.name).list
20 | }
21 |
22 | def createRegistry(name: String, description: Option[String], overwrite: Boolean, isPrivate: Boolean)
23 | (implicit s: Session): Unit = {
24 | Registries.insert(Registry(name, description, overwrite, isPrivate))
25 |
26 | val dir = new File(s"${RegistryPath}/${name}")
27 | dir.mkdirs()
28 | }
29 |
30 | def updateRegistry(name: String, description: Option[String], overwrite: Boolean, isPrivate: Boolean)
31 | (implicit s: Session): Unit = {
32 | Registries.filter(_.name === name.bind)
33 | .map(t => (t.description.?, t.overwrite, t.isPrivate))
34 | .update((description, overwrite, isPrivate))
35 | }
36 |
37 | def deleteRegistry(name: String)(implicit s: Session): Unit = {
38 | Registries.filter(_.name === name.bind).delete
39 |
40 | val dir = new File(s"${RegistryPath}/${name}")
41 | FileUtils.deleteDirectory(dir)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/twirl/gitbucket/mavenrepository/files.scala.html:
--------------------------------------------------------------------------------
1 | @(name: String, path: String, files: Seq[java.io.File])(implicit context: gitbucket.core.controller.Context)
2 | @import gitbucket.core.view.helpers._
3 | @gitbucket.core.html.main(s"$name - /$path"){
4 |
69 | }
70 | @context.loginAccount.collect { case account if account.isAdmin =>
71 |
83 | }
--------------------------------------------------------------------------------
/src/main/twirl/gitbucket/mavenrepository/form.scala.html:
--------------------------------------------------------------------------------
1 | @(registry: Option[io.github.gitbucket.mavenrepository.model.Registry])(implicit context: gitbucket.core.controller.Context)
2 | @gitbucket.core.html.main("Maven repositories") {
3 | @gitbucket.core.admin.html.menu("maven") {
4 |
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/twirl/gitbucket/mavenrepository/settings.scala.html:
--------------------------------------------------------------------------------
1 | @(registries: Seq[io.github.gitbucket.mavenrepository.model.Registry])(implicit context: gitbucket.core.controller.Context)
2 | @gitbucket.core.html.main("Maven repositories") {
3 | @gitbucket.core.admin.html.menu("maven") {
4 |
7 | Maven repositories
8 |
9 |
10 | Name |
11 | Description |
12 | Type |
13 | URL |
14 | Overwrite |
15 | Private |
16 | |
17 |
18 | @registries.map { registry =>
19 |
20 | @registry.name |
21 | @registry.description |
22 | Maven |
23 | @context.baseUrl/maven/@registry.name/ |
24 | @registry.overwrite |
25 | @registry.isPrivate |
26 |
27 |
28 | Edit
29 | Delete
30 |
31 | |
32 |
33 | }
34 |
35 |
36 | }
37 | }
38 |
48 |
49 |
--------------------------------------------------------------------------------