1- #! / usr/ bin/ env kotlin
2-
3- @file:DependsOn(" com.mysql:mysql-connector-j:9.5.0" )
4- @file:DependsOn(" org.eclipse.jgit:org.eclipse.jgit:7.4.0.202509020913-r" )
5- @file:DependsOn(" com.squareup.okio:okio:3.16.4" )
6-
71import org.eclipse.jgit.api.Git
2+ import org.slf4j.LoggerFactory
83import java.io.IOException
94import java.net.HttpURLConnection
10- import java.net.URL
5+ import java.net.URI
116import java.nio.file.Files
127import java.nio.file.Path
138import java.nio.file.Paths
@@ -20,6 +15,9 @@ import java.util.zip.ZipEntry
2015import java.util.zip.ZipOutputStream
2116import kotlin.io.path.inputStream
2217
18+ private val log = LoggerFactory .getLogger(" CoopDeployer" )
19+
20+
2321// --------------------------- CONFIG ---------------------------
2422
2523val PATCH_VERSION = System .getenv(" PATCH_VERSION" ) ? : error(" PATCH_VERSION required" )
@@ -68,7 +66,7 @@ fun setPerm664(path: Path) {
6866 }
6967 Files .setPosixFilePermissions(path, perms)
7068 } catch (e: Exception ) {
71- println (" Warning: couldn't set perms on $path : ${e.message} " )
69+ log.warn (" Warning: couldn't set perms on {} " , path, e )
7270 }
7371}
7472
@@ -111,13 +109,13 @@ fun zipPreserveStructure(sources: List<Path>, outputFile: Path, base: Path) {
111109fun prepareRepo (): Path {
112110 val dir = Paths .get(WORKDIR )
113111 if (Files .exists(dir.resolve(" .git" ))) {
114- println (" Repo exists — fetching and checking out $GIT_REF ..." )
112+ log.info (" Repo exists — fetching and checking out $GIT_REF ..." )
115113 Git .open(dir.toFile()).use { git ->
116114 git.fetch().call()
117115 git.checkout().setName(GIT_REF ).call()
118116 }
119117 } else {
120- println (" Cloning $REPO_URL -> $dir ..." )
118+ log.info (" Cloning $REPO_URL -> $dir ..." )
121119 Git .cloneRepository()
122120 .setURI(REPO_URL )
123121 .setDirectory(dir.toFile())
@@ -132,7 +130,7 @@ fun prepareRepo(): Path {
132130// --------------------------- GITHUB RELEASE ASSET DOWNLOAD ---------------------------
133131
134132fun httpGet (url : String ): String {
135- val conn = URL (url).openConnection() as HttpURLConnection
133+ val conn = URI .create (url).toURL( ).openConnection() as HttpURLConnection
136134 conn.requestMethod = " GET"
137135 conn.setRequestProperty(" Accept" , " application/vnd.github.v3+json" )
138136 conn.connectTimeout = 15000
@@ -143,7 +141,7 @@ fun httpGet(url: String): String {
143141}
144142
145143fun downloadFile (url : String , dest : Path ) {
146- val u = URL (url)
144+ val u = URI .create (url).toURL( )
147145 val conn = u.openConnection() as HttpURLConnection
148146 conn.requestMethod = " GET"
149147 conn.connectTimeout = 20000
@@ -168,36 +166,36 @@ fun findNx2UrlsFromReleaseJson(json: String): List<String> {
168166}
169167
170168fun downloadVoAssets (version : String , targetDir : Path ): List <Path > {
171- println (" Downloading VO assets for version $version from GitHub releases..." )
169+ log.info (" Downloading VO assets for version $version from GitHub releases..." )
172170 Files .createDirectories(VO_DOWNLOAD_TMP )
173171 val apiUrl = " https://api.github.com/repos/FAForever/fa-coop/releases/tags/v$version "
174172 val json = try {
175173 httpGet(apiUrl)
176174 } catch (e: Exception ) {
177- println (" Warning: failed to fetch release JSON: ${e.message} " )
175+ log.warn (" Warning: failed to fetch release JSON" , e )
178176 return emptyList()
179177 }
180178
181179 val urls = findNx2UrlsFromReleaseJson(json)
182180 if (urls.isEmpty()) {
183- println (" No .nx2 assets found in release v$version " )
181+ log.info (" No .nx2 assets found in release v{} " , version )
184182 return emptyList()
185183 }
186184
187185 val downloaded = mutableListOf<Path >()
188186 for (u in urls) {
189- val filename = Paths .get(URL (u ).path).fileName.toString()
187+ val filename = Paths .get(URI .create(u).toURL( ).path).fileName.toString()
190188 val dst = VO_DOWNLOAD_TMP .resolve(filename)
191189 try {
192- println (" Downloading $u -> $dst " )
190+ log.info (" Downloading {} -> {} " ,u, dst )
193191 downloadFile(u, dst)
194192 // rename to include .v{version}.nx2 before .nx2
195193 val newName = filename.replace(Regex (" \\ .nx2$" ), " .v$version .nx2" )
196194 val newPath = VO_DOWNLOAD_TMP .resolve(newName)
197195 Files .move(dst, newPath, StandardCopyOption .REPLACE_EXISTING )
198196 downloaded.add(newPath)
199197 } catch (e: Exception ) {
200- println (" Warning: failed to download $u : ${e.message} " )
198+ log.warn (" Warning: failed to download {} " , u, e )
201199 }
202200 }
203201
@@ -206,16 +204,16 @@ fun downloadVoAssets(version: String, targetDir: Path): List<Path> {
206204 Files .createDirectories(outDir)
207205 for (p in downloaded) {
208206 val dest = outDir.resolve(p.fileName.toString())
209- println (" Copying VO $p -> $dest " )
207+ log.info (" Copying VO {} -> {} " , p, dest )
210208 try {
211209 if (! DRYRUN ) {
212210 Files .copy(p, dest, StandardCopyOption .REPLACE_EXISTING )
213211 setPerm664(dest)
214212 } else {
215- println (" [DRYRUN] Would copy $p -> $dest " )
213+ log.info (" [DRYRUN] Would copy {} -> {} " , p, dest )
216214 }
217215 } catch (e: Exception ) {
218- println (" Warning: copying failed: ${e.message} " )
216+ log.warn (" Warning: copying failed" , e )
219217 }
220218 }
221219
@@ -225,7 +223,7 @@ fun downloadVoAssets(version: String, targetDir: Path): List<Path> {
225223// --------------------------- DATABASE ---------------------------
226224
227225fun dbConnection () =
228- DriverManager .getConnection(" jdbc:mysql ://$DB_HOST /$DB_NAME ?useSSL=false&serverTimezone=UTC" , DB_USER , DB_PASS )
226+ DriverManager .getConnection(" jdbc:mariadb ://$DB_HOST /$DB_NAME ?useSSL=false&serverTimezone=UTC" , DB_USER , DB_PASS )
229227
230228fun readExisting (conn : java.sql.Connection , mod : String ): Map <Int , Pair <String ?, String ?>> {
231229 val sql = """
@@ -248,9 +246,9 @@ fun readExisting(conn: java.sql.Connection, mod: String): Map<Int, Pair<String?,
248246}
249247
250248fun updateDb (conn : java.sql.Connection , mod : String , fileId : Int , version : Int , name : String , md5 : String ) {
251- println (" Updating DB: $name (fileId=$fileId , version=$version ) md5=$md5 " )
249+ log.info (" Updating DB: {} (fileId={} , version={} ) md5={} " , name, fileId, version, md5 )
252250 if (DRYRUN ) {
253- println (" [DRYRUN] DB: would delete/insert for $ fileId ,$ version" )
251+ log.info (" [DRYRUN] DB: would delete/insert for {},{} " , fileId, version)
254252 return
255253 }
256254 val del = " DELETE FROM updates_${mod} _files WHERE fileId=? AND version=?"
@@ -285,49 +283,49 @@ fun processItem(
285283 Files .createDirectories(outDir)
286284 val target = outDir.resolve(name)
287285
288- println (" Processing $name (fileId $fileId ) " )
286+ log.info (" Processing {} (fileId {}) " , name, fileId )
289287
290288 if (sources == null ) {
291289 // VO file: look for downloaded file in target dir (name formatted)
292290 val expectedName = name // matches nameFmt.format(version)
293291 val candidate = outDir.resolve(expectedName)
294292 if (! Files .exists(candidate)) {
295- println (" Warning: VO file $expectedName not found in $outDir , skipping" )
293+ log.info (" Warning: VO file {} not found in {} , skipping" , expectedName, outDir )
296294 return
297295 }
298296 val newMd5 = md5(candidate)
299297 val oldMd5 = readExisting(conn, mod)[fileId]?.second
300298 if (newMd5 != oldMd5) {
301299 updateDb(conn, mod, fileId, version, expectedName, newMd5)
302300 } else {
303- println (" VO $expectedName unchanged" )
301+ log.info (" VO {} unchanged" , expectedName )
304302 }
305303 return
306304 }
307305
308306 // sources present -> create zip or copy single file
309307 val existing = sources.filter { p -> Files .exists(p) }
310308 if (existing.isEmpty()) {
311- println (" Warning: no existing sources for $name , skipping" )
309+ log.info (" Warning: no existing sources for {} , skipping" , name )
312310 return
313311 }
314312
315313 // if single source and it's a file, copy it directly (like init_coop.lua)
316314 if (existing.size == 1 && Files .isRegularFile(existing[0 ])) {
317315 val src = existing[0 ]
318- println (" Single file source for $name : copying $src -> $target " )
316+ log.info (" Single file source for {} : copying {} -> {} " , name, src, target )
319317 if (! DRYRUN ) {
320318 Files .copy(src, target, StandardCopyOption .REPLACE_EXISTING )
321319 setPerm664(target)
322320 } else {
323- println (" [DRYRUN] Would copy $src -> $target " )
321+ log.info (" [DRYRUN] Would copy {} -> {} " , src, target )
324322 }
325323 val newMd5 = md5(target)
326324 val oldMd5 = readExisting(conn, mod)[fileId]?.second
327325 if (newMd5 != oldMd5) {
328326 updateDb(conn, mod, fileId, version, name, newMd5)
329327 } else {
330- println ( " $name unchanged" )
328+ log.info( " {} unchanged" , name )
331329 }
332330 return
333331 }
@@ -340,85 +338,86 @@ fun processItem(
340338 }
341339
342340 val tmp = Files .createTempFile(" coop" , " .zip" )
343- println (" Zipping sources with base=$base -> $tmp " )
341+ log.info (" Zipping sources with base={} -> {} " , base, tmp )
344342 zipPreserveStructure(existing, tmp, base)
345- println (" Moving zip to $target " )
343+ log.info (" Moving zip to {} " , target )
346344 if (! DRYRUN ) {
347345 Files .move(tmp, target, StandardCopyOption .REPLACE_EXISTING )
348346 setPerm664(target)
349347 } else {
350- println (" [DRYRUN] Would move $tmp -> $target " )
348+ log.info (" [DRYRUN] Would move {} -> {} " , tmp, target )
351349 }
352350
353351 val newMd5 = md5(target)
354352 val oldMd5 = readExisting(conn, mod)[fileId]?.second
355353 if (newMd5 != oldMd5) {
356354 updateDb(conn, mod, fileId, version, name, newMd5)
357355 } else {
358- println ( " $name unchanged" )
356+ log.info( " {} unchanged" , name )
359357 }
360358}
361359
362360// --------------------------- MAIN ---------------------------
363-
364- println (" === Kotlin Coop Deployer v$PATCH_VERSION ===" )
365- val repo = prepareRepo()
366- println (" Repo ready at $repo " )
361+ fun main () {
362+ log.info (" === Kotlin Coop Deployer v{} ===" , PATCH_VERSION )
363+ val repo = prepareRepo()
364+ log.info (" Repo ready at {} " , repo )
367365
368366// Download VO assets first
369- val voFiles = downloadVoAssets(PATCH_VERSION , TARGET_DIR ) // returns list of paths in target dir
370- val voMap = voFiles.associateBy { it.fileName.toString() } // name -> Path
371-
372- val conn = dbConnection()
373- try {
374- val existing = readExisting(conn, " coop" )
375-
376- data class PatchFile (val id : Int , val fileTemplate : String , val includes : List <Path >? )
377-
378- val filesList = listOf (
379- PatchFile (1 , " init_coop.v%d.lua" , listOf (repo.resolve(" init_coop.lua" ))),
380- PatchFile (
381- 2 , " lobby_coop_v%d.cop" , listOf (
382- repo.resolve(" mods" ),
383- repo.resolve(" units" ),
384- repo.resolve(" mod_info.lua" ),
385- repo.resolve(" readme.md" ),
386- repo.resolve(" changelog.md" )
387- )
388- ),
389-
390- // all VO files (no sources → already downloaded externally)
391- PatchFile (3 , " A01_VO.v%d.nx2" , null ),
392- PatchFile (4 , " A02_VO.v%d.nx2" , null ),
393- PatchFile (5 , " A03_VO.v%d.nx2" , null ),
394- PatchFile (6 , " A04_VO.v%d.nx2" , null ),
395- PatchFile (7 , " A05_VO.v%d.nx2" , null ),
396- PatchFile (8 , " A06_VO.v%d.nx2" , null ),
397- PatchFile (9 , " C01_VO.v%d.nx2" , null ),
398- PatchFile (10 , " C02_VO.v%d.nx2" , null ),
399- PatchFile (11 , " C03_VO.v%d.nx2" , null ),
400- PatchFile (12 , " C04_VO.v%d.nx2" , null ),
401- PatchFile (13 , " C05_VO.v%d.nx2" , null ),
402- PatchFile (14 , " C06_VO.v%d.nx2" , null ),
403- PatchFile (15 , " E01_VO.v%d.nx2" , null ),
404- PatchFile (16 , " E02_VO.v%d.nx2" , null ),
405- PatchFile (17 , " E03_VO.v%d.nx2" , null ),
406- PatchFile (18 , " E04_VO.v%d.nx2" , null ),
407- PatchFile (19 , " E05_VO.v%d.nx2" , null ),
408- PatchFile (20 , " E06_VO.v%d.nx2" , null ),
409- PatchFile (21 , " Prothyon16_VO.v%d.nx2" , null ),
410- PatchFile (22 , " A03_VO.v%d.nx2" , null ),
411- PatchFile (23 , " A03_VO.v%d.nx2" , null ),
412- PatchFile (24 , " A03_VO.v%d.nx2" , null ),
413- PatchFile (25 , " A03_VO.v%d.nx2" , null ),
414- // … add the rest
415- )
416-
417- for ((fileId, fmt, srcs) in filesList) {
418- processItem(conn, " coop" , PATCH_VERSION .toInt(), fileId, fmt, srcs?.map { it }, voMap)
367+ val voFiles = downloadVoAssets(PATCH_VERSION , TARGET_DIR ) // returns list of paths in target dir
368+ val voMap = voFiles.associateBy { it.fileName.toString() } // name -> Path
369+
370+ val conn = dbConnection()
371+ try {
372+ val existing = readExisting(conn, " coop" )
373+
374+ data class PatchFile (val id : Int , val fileTemplate : String , val includes : List <Path >? )
375+
376+ val filesList = listOf (
377+ PatchFile (1 , " init_coop.v%d.lua" , listOf (repo.resolve(" init_coop.lua" ))),
378+ PatchFile (
379+ 2 , " lobby_coop_v%d.cop" , listOf (
380+ repo.resolve(" mods" ),
381+ repo.resolve(" units" ),
382+ repo.resolve(" mod_info.lua" ),
383+ repo.resolve(" readme.md" ),
384+ repo.resolve(" changelog.md" )
385+ )
386+ ),
387+
388+ // all VO files (no sources → already downloaded externally)
389+ PatchFile (3 , " A01_VO.v%d.nx2" , null ),
390+ PatchFile (4 , " A02_VO.v%d.nx2" , null ),
391+ PatchFile (5 , " A03_VO.v%d.nx2" , null ),
392+ PatchFile (6 , " A04_VO.v%d.nx2" , null ),
393+ PatchFile (7 , " A05_VO.v%d.nx2" , null ),
394+ PatchFile (8 , " A06_VO.v%d.nx2" , null ),
395+ PatchFile (9 , " C01_VO.v%d.nx2" , null ),
396+ PatchFile (10 , " C02_VO.v%d.nx2" , null ),
397+ PatchFile (11 , " C03_VO.v%d.nx2" , null ),
398+ PatchFile (12 , " C04_VO.v%d.nx2" , null ),
399+ PatchFile (13 , " C05_VO.v%d.nx2" , null ),
400+ PatchFile (14 , " C06_VO.v%d.nx2" , null ),
401+ PatchFile (15 , " E01_VO.v%d.nx2" , null ),
402+ PatchFile (16 , " E02_VO.v%d.nx2" , null ),
403+ PatchFile (17 , " E03_VO.v%d.nx2" , null ),
404+ PatchFile (18 , " E04_VO.v%d.nx2" , null ),
405+ PatchFile (19 , " E05_VO.v%d.nx2" , null ),
406+ PatchFile (20 , " E06_VO.v%d.nx2" , null ),
407+ PatchFile (21 , " Prothyon16_VO.v%d.nx2" , null ),
408+ PatchFile (22 , " A03_VO.v%d.nx2" , null ),
409+ PatchFile (23 , " A03_VO.v%d.nx2" , null ),
410+ PatchFile (24 , " A03_VO.v%d.nx2" , null ),
411+ PatchFile (25 , " A03_VO.v%d.nx2" , null ),
412+ // … add the rest
413+ )
414+
415+ for ((fileId, fmt, srcs) in filesList) {
416+ processItem(conn, " coop" , PATCH_VERSION .toInt(), fileId, fmt, srcs?.map { it }, voMap)
417+ }
418+ } finally {
419+ conn.close()
419420 }
420- } finally {
421- conn.close()
422- }
423421
424- println (" === Done ===" )
422+ log.info(" === Done ===" )
423+ }
0 commit comments