mirror of
https://github.com/coder/registry.git
synced 2026-06-02 20:48:14 +00:00
Add [copyparty] module (#486)
## Description This PR adds a module to install Copyparty as an alternative to Filebrowser. ## Type of Change - [x] New module - [ ] New template - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/djarbz/modules/copyparty` **New version:** `v0.1.0` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [N/A] Tests pass (`bun test`) - [x] Code formatted (`bun fmt`) - [x] Changes tested locally ## Related Issues <!-- Link related issues or write "None" if not applicable --> None --------- Co-authored-by: DevCats <christofer@coder.com>
This commit is contained in:
@@ -0,0 +1,210 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="300mm"
|
||||||
|
height="207mm"
|
||||||
|
viewBox="0 0 300 207"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<title
|
||||||
|
id="title1">copyparty_logo</title>
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient1">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffcc55;stop-opacity:1"
|
||||||
|
offset="0"
|
||||||
|
id="stop1" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffcc00;stop-opacity:1"
|
||||||
|
offset="0.2"
|
||||||
|
id="stop2" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ff8800;stop-opacity:1"
|
||||||
|
offset="1"
|
||||||
|
id="stop3" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1"
|
||||||
|
id="linearGradient2"
|
||||||
|
x1="15"
|
||||||
|
y1="15"
|
||||||
|
x2="15"
|
||||||
|
y2="143"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>copyparty_logo</dc:title>
|
||||||
|
<dc:source>github.com/9001/copyparty</dc:source>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="kassett">
|
||||||
|
<rect
|
||||||
|
style="fill:#333333"
|
||||||
|
id="rect1"
|
||||||
|
width="300"
|
||||||
|
height="205"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
rx="12"
|
||||||
|
ry="12" />
|
||||||
|
<rect
|
||||||
|
style="fill:url(#linearGradient2)"
|
||||||
|
id="rect2"
|
||||||
|
width="270"
|
||||||
|
height="128"
|
||||||
|
x="15"
|
||||||
|
y="15"
|
||||||
|
rx="8"
|
||||||
|
ry="8" />
|
||||||
|
<rect
|
||||||
|
style="fill:#333333"
|
||||||
|
id="rect3"
|
||||||
|
width="172"
|
||||||
|
height="52"
|
||||||
|
x="64"
|
||||||
|
y="72"
|
||||||
|
rx="26"
|
||||||
|
ry="26" />
|
||||||
|
<circle
|
||||||
|
style="fill:#cccccc"
|
||||||
|
id="circle1"
|
||||||
|
cx="91"
|
||||||
|
cy="98"
|
||||||
|
r="18" />
|
||||||
|
<circle
|
||||||
|
style="fill:#cccccc"
|
||||||
|
id="circle2"
|
||||||
|
cx="209"
|
||||||
|
cy="98"
|
||||||
|
r="18" />
|
||||||
|
<path
|
||||||
|
style="fill:#737373;stroke-width:1px"
|
||||||
|
d="m 48,207 10,-39 c 1.79,-6.2 5.6,-7.8 12,-8 60,-1 100,-1 160,0 6.4,0.2 10,1.8 12,8 l 10,39 z"
|
||||||
|
id="path1"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer3"
|
||||||
|
inkscape:label="tekst"
|
||||||
|
style="display:none">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:38.8056px;line-height:1.25;font-family:Akbar;-inkscape-font-specification:Akbar;letter-spacing:3.70417px;word-spacing:0px;fill:#333333"
|
||||||
|
x="47.153069"
|
||||||
|
y="55.548954"
|
||||||
|
id="text1"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan1"
|
||||||
|
x="47.153069"
|
||||||
|
y="55.548954"
|
||||||
|
style="-inkscape-font-specification:Akbar"
|
||||||
|
rotate="0 0">copyparty</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="stensatt">
|
||||||
|
<path
|
||||||
|
d="m 63.5,50.9 q -0.85,0.93 -4.73,2.3 -3.6,1.3 -4.4,1.3 -3.3,0 -5.1,-2.1 -1.75,-2 -1.75,-5.36 0,-4.6 3.76,-7.64 3.3,-2.7 7.3,-2.7 0.4,0 0.93,0.74 0.54,0.7 0.54,1.16 0,2.06 -2.2,2.7 -1.36,0.4 -4.04,1.16 -2.2,1.16 -2.2,4.4 0,3.2 2.9,3.2 0.85,0 0.85,0 0.54,0 1.44,-0.16 1.1,-0.23 2.9,-0.74 1.8,-0.54 2.13,-0.54 0.4,0 1.75,0.6 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path11" />
|
||||||
|
<path
|
||||||
|
d="m 87.6,45 q 0,4.2 -3.7,6.95 -3.2,2.3 -6.87,2.3 -3.4,0 -6,-2.6 -2.5,-2.6 -2.5,-6 0,-3.6 3.14,-6.64 3.2,-3 6.8,-3 3.5,0 6.3,2.76 2.83,2.76 2.83,6.25 z m -3.4,0.16 q 0,-2.25 -1.75,-3.7 -1.7,-1.5 -4,-1.5 -0.1,0 -1.6,1.6 -1.44,1.55 -2.44,1.55 -0.6,0 -0.8,-0.3 -1.16,2.3 -1.16,3 0,2.25 2.13,3.4 1.6,0.9 3.6,0.9 2,0 3.76,-1.1 2.25,-1.4 2.25,-3.84 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path12" />
|
||||||
|
<path
|
||||||
|
d="m 112.8,46.8 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.7,1.5 -0.7,0 -2.7,-0.4 -1.9,-0.4 -2.6,-0.4 -2.1,0 -2.1,2.64 0,0.85 0.23,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.77,2.83 -1.44,0 -3,-0.85 -1.46,-9.5 -1.46,-12 0,-3.65 1.75,-8.1 2.37,-6.05 6.45,-6.05 3.7,0 7.3,4.1 3.3,3.84 3.3,7.14 z m -3.8,0.2 q -0.6,-2.2 -2.6,-4.4 -2.3,-2.5 -4.3,-2.5 -1.3,0 -2.33,2.2 -0.9,1.8 -0.9,3.26 0,0.47 0.38,1.24 0.43,0.8 0.85,0.8 1.1,0 3.2,0.3 2.1,0.3 3.2,0.3 0.3,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path13" />
|
||||||
|
<path
|
||||||
|
d="m 133,40 q -2.1,4.1 -3.2,7 -0.1,0.3 -1.6,4.5 -0.4,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.3,2.64 -1.4,-0.2 -1.6,-1.6 0,-0.2 0,-0.5 0,-0.16 0.3,-1.5 1,-5.04 1,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.5,-1.36 2.1,-1.36 0.4,0 1.1,0.6 0.6,0.6 0.7,1.1 0.8,6.2 4.9,11.1 1,-1.8 1.8,-4.04 0.5,-1.4 1.6,-4.15 1.9,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.9,0.3 1.3,2.8 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path14" />
|
||||||
|
<path
|
||||||
|
d="m 157.5,48 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.7,1.5 -0.7,0 -2.7,-0.4 -1.9,-0.4 -2.6,-0.4 -2,0 -2,2.64 0,0.85 0.2,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.7,2.83 -1.5,0 -3,-0.85 -1.5,-9.5 -1.5,-11.95 0,-3.65 1.8,-8.1 2.3,-6.05 6.4,-6.05 3.7,0 7.2,4.1 3.3,3.84 3.3,7.14 z m -3.8,0.2 q -0.6,-2.2 -2.6,-4.4 -2.3,-2.5 -4.3,-2.5 -1.3,0 -2.3,2.2 -0.9,1.8 -0.9,3.26 0,0.47 0.4,1.24 0.4,0.8 0.8,0.8 1.1,0 3.2,0.3 2.1,0.3 3.2,0.3 0.3,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path15" />
|
||||||
|
<path
|
||||||
|
d="m 182,53.3 q 0,0.9 -0.6,1.5 -0.6,0.6 -1.4,0.6 -1.6,0 -3,-0.9 -1.4,-0.93 -2.1,-2.3 -0.7,-0.1 -1.5,0.85 -0.9,1.16 -1.1,1.24 -1.2,0.54 -3.9,0.54 -2.2,0 -3.9,-2.44 -1.5,-2.13 -1.5,-4 0,-3.4 3.4,-6.4 3.2,-2.9 6.7,-2.9 0.9,0 1.7,0.6 0.8,0.6 0.8,1.44 0,0.54 -0.4,1.1 2.4,0.9 2.4,2.83 0,0.35 -0.1,1.05 -0.1,0.7 -0.1,1.05 0,0.4 0.1,0.6 0.5,1.3 2.5,3.4 1.9,1.9 1.9,2.2 z m -8.1,-10.1 q -0.4,0 -1.1,-0.1 -0.8,-0.16 -1.1,-0.16 -1.3,0 -3.2,1.94 -1.9,1.94 -1.9,3.3 0,0.8 0.7,1.8 0.9,1.3 2.2,1.3 2.6,0 3.5,-2.9 0.5,-2.6 1,-5.16 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path16" />
|
||||||
|
<path
|
||||||
|
d="m 203.8,42.4 q -0.4,0.4 -1.5,0.4 -0.9,0 -2.5,-0.3 -1.7,-0.3 -2.5,-0.3 -4.7,0 -5.5,6.9 -0.3,3.1 -0.4,3.3 -0.4,1 -1.7,2.3 h -1.1 q -0.7,-1.2 -1.3,-4.1 -0.6,-2.76 -0.6,-4.27 0,-1.16 0.1,-1.5 0.2,-0.54 1,-0.54 0.3,0 0.6,0.3 0.4,0.3 0.4,0.3 1.9,-3.53 3.1,-4.6 1.8,-1.7 5.1,-1.7 1.4,0 3.6,0.9 2.8,1.16 3.3,2.8 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path17" />
|
||||||
|
<path
|
||||||
|
d="m 229.5,37.16 q 0.3,0.8 0.3,1.44 0,1.86 -2.4,1.86 -1,0 -3.5,-0.5 -2.5,-0.54 -3.4,-0.54 -1.3,0 -1.5,0.1 -0.4,0.2 -0.4,1.2 0,2.2 0.6,6.9 0.7,5.86 1.6,6.13 -0.4,0.35 -0.4,1.1 -1.2,0.7 -2.6,0.7 -1.4,0 -2,-3.9 -0.2,-1.36 -0.5,-7.76 -0.2,-4.6 -0.8,-5.5 -0.3,-0.47 -4.3,-0.35 -1,0 -1.6,0.1 -0.5,0 -0.3,0 -0.8,0 -1.2,-0.7 -0.5,-1.3 -0.5,-1.4 0,-1.44 4.1,-2 1.6,-0.16 4.7,-0.5 0,-0.85 -0.1,-2.56 0,-1.75 0,-2.6 0,-4.35 2.1,-4.35 0.5,0 1.1,0.6 0.6,0.6 0.6,1.1 v 7.9 q 1.1,1.2 5,1.7 3.9,0.5 5.3,1.86 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path18" />
|
||||||
|
<path
|
||||||
|
d="m 251.2,40.2 q -2,4.1 -3.2,7 -0.1,0.3 -1.5,4.5 -0.5,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.4,2.64 -1.4,-0.2 -1.5,-1.6 -0.1,-0.2 -0.1,-0.5 0,-0.16 0.3,-1.5 1.1,-5.04 1.1,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.4,-1.36 2.1,-1.36 0.4,0 1,0.6 0.6,0.6 0.7,1.1 0.9,6.2 4.9,11.1 1,-1.8 1.9,-4.04 0.5,-1.4 1.6,-4.15 1.8,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.8,0.3 1.2,2.8 z"
|
||||||
|
style="fill:#333333"
|
||||||
|
id="path19" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer5"
|
||||||
|
inkscape:label="tagger">
|
||||||
|
<g
|
||||||
|
id="g1">
|
||||||
|
<path
|
||||||
|
id="path4"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path5"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path6"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g2"
|
||||||
|
transform="rotate(30,150,318.19)">
|
||||||
|
<path
|
||||||
|
id="path7"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path8"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
<path
|
||||||
|
id="path9"
|
||||||
|
style="fill:#333333"
|
||||||
|
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
|
||||||
|
sodipodi:nodetypes="cccccccccc" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
display_name: "Austin"
|
||||||
|
bio: "IT Pro by day, script kiddie at night."
|
||||||
|
avatar: "./.images/avatar.png"
|
||||||
|
github: "djarbz"
|
||||||
|
status: "community"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Austin
|
||||||
|
|
||||||
|
I like to program as a hobby.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
display_name: copyparty
|
||||||
|
description: A web based file explorer alternative to Filebrowser.
|
||||||
|
icon: ../../../../.icons/copyparty.svg
|
||||||
|
verified: false
|
||||||
|
tags: [files, filebrowser, web, copyparty]
|
||||||
|
---
|
||||||
|
|
||||||
|
# copyparty
|
||||||
|
|
||||||
|
<!-- Describes what this module does -->
|
||||||
|
|
||||||
|
This module installs Copyparty, an alternative to Filebrowser.
|
||||||
|
[Copyparty](https://github.com/9001/copyparty) is a portable file server with accelerated resumable uploads, dedup, WebDAV, FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file, no deps
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "copyparty" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/djarbz/copyparty/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Add a screencast or screenshot here put them in .images directory -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Example 1
|
||||||
|
|
||||||
|
Some basic command line options:
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "copyparty" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/djarbz/copyparty/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
arguments = [
|
||||||
|
"-v", "/home/coder/:/home:r", # Share home directory (read-only)
|
||||||
|
"-v", "${local.repo_dir}:/repo:rw", # Share project directory (read-write)
|
||||||
|
"-e2dsa", # Enables general file indexing"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2
|
||||||
|
|
||||||
|
```tf
|
||||||
|
module "copyparty" {
|
||||||
|
count = data.coder_workspace.me.start_count
|
||||||
|
source = "registry.coder.com/djarbz/copyparty/coder"
|
||||||
|
version = "1.0.0"
|
||||||
|
agent_id = coder_agent.example.id
|
||||||
|
subdomain = true
|
||||||
|
arguments = [
|
||||||
|
"-v", "/tmp:/tmp:r", # Share tmp directory (read-only)
|
||||||
|
"-v", "/home/coder/:/home:rw", # Share home directory (read-write)
|
||||||
|
"-v", "${local.root_dir}:/work:A:c,dotsrch", # Share work directory (All Perms)
|
||||||
|
"-e2dsa", # Enables general file indexing"
|
||||||
|
"--re-maxage", "900", # Rescan filesystem for changes every SEC
|
||||||
|
"--see-dots", # Show dotfiles by default if user has correct permissions on volume
|
||||||
|
"--xff-src=lan", # List of trusted reverse-proxy CIDRs (comma-separated) or `lan` for private IPs.
|
||||||
|
"--rproxy", "1", # Which ip to associate clients with, index of X-FWD IP.
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# --- Test Case 1: Required Variables ---
|
||||||
|
run "plan_with_required_vars" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "example-agent-id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Test Case 2: Coder App URL uses custom port ---
|
||||||
|
run "app_url_uses_port" {
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "example-agent-id"
|
||||||
|
port = 19999
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.url == "http://localhost:19999"
|
||||||
|
error_message = "Expected copyparty app URL to include configured port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Test Case 3: Default Values ---
|
||||||
|
run "test_defaults" {
|
||||||
|
# This run block applies the module with default values
|
||||||
|
# (except for the required 'agent_id' provided above).
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "example-agent-id"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Asserts for coder_app "copyparty" ---
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.display_name == "copyparty"
|
||||||
|
error_message = "Default display_name is incorrect"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.slug == "copyparty"
|
||||||
|
error_message = "Default slug is incorrect"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.url == "http://localhost:3923"
|
||||||
|
error_message = "Default URL is incorrect, expected port 3923"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.subdomain == false
|
||||||
|
error_message = "Default subdomain should be false"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.share == "owner"
|
||||||
|
error_message = "Default share value should be 'owner'"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.open_in == "slim-window"
|
||||||
|
error_message = "Default open_in value should be 'slim-window'"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Asserts for coder_script "copyparty" ---
|
||||||
|
assert {
|
||||||
|
condition = coder_script.copyparty.display_name == "copyparty"
|
||||||
|
error_message = "Script display_name is incorrect"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check rendered script content (this assumes your run.sh uses the variables)
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "PORT=\"3923\"")
|
||||||
|
error_message = "Script content does not reflect default port"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "LOG_PATH=\"/tmp/copyparty.log\"")
|
||||||
|
error_message = "Script content does not reflect default log_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "IFS=',' read -r -a ARGUMENTS \u003c\u003c\u003c \"\"")
|
||||||
|
error_message = "Script content does not reflect default empty arguments"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Test Case 4: Custom Values ---
|
||||||
|
run "test_custom_values" {
|
||||||
|
# Override default variables for this specific run
|
||||||
|
variables {
|
||||||
|
agent_id = "example-agent-id"
|
||||||
|
port = 8080
|
||||||
|
slug = "my-custom-app"
|
||||||
|
display_name = "My Custom App"
|
||||||
|
share = "authenticated"
|
||||||
|
open_in = "tab"
|
||||||
|
pinned_version = "v1.2.3"
|
||||||
|
arguments = ["--verbose", "-v"]
|
||||||
|
log_path = "/var/log/custom.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Asserts for coder_app "copyparty" ---
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.display_name == "My Custom App"
|
||||||
|
error_message = "Custom display_name was not applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.slug == "my-custom-app"
|
||||||
|
error_message = "Custom slug was not applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.url == "http://localhost:8080"
|
||||||
|
error_message = "Custom port was not applied to URL"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.share == "authenticated"
|
||||||
|
error_message = "Custom share value was not applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = resource.coder_app.copyparty.open_in == "tab"
|
||||||
|
error_message = "Custom open_in value was not applied"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Asserts for coder_script "copyparty" ---
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "PORT=\"8080\"")
|
||||||
|
error_message = "Script content does not reflect custom port"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "PINNED_VERSION=\"v1.2.3\"")
|
||||||
|
error_message = "Script content does not reflect custom pinned_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "IFS=',' read -r -a ARGUMENTS \u003c\u003c\u003c \"--verbose,-v\"")
|
||||||
|
error_message = "Script content does not reflect custom arguments"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = strcontains(coder_script.copyparty.script, "LOG_PATH=\"/var/log/custom.log\"")
|
||||||
|
error_message = "Script content does not reflect custom log_path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Test Case 5: Validation Failure (open_in) ---
|
||||||
|
run "test_invalid_open_in" {
|
||||||
|
# This is a 'plan' test that expects a failure
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "example-agent-id"
|
||||||
|
open_in = "invalid-value"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Expect this plan to fail due to the validation rule in 'var.open_in'
|
||||||
|
expect_failures = [
|
||||||
|
var.open_in,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Test Case 6: Validation Failure (share) ---
|
||||||
|
run "test_invalid_share" {
|
||||||
|
# This is a 'plan' test that expects a failure
|
||||||
|
command = plan
|
||||||
|
|
||||||
|
variables {
|
||||||
|
agent_id = "example-agent-id"
|
||||||
|
share = "everyone" # This is not 'owner', 'authenticated', or 'public'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Expect this plan to fail due to the validation rule in 'var.share'
|
||||||
|
expect_failures = [
|
||||||
|
var.share,
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.0"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
coder = {
|
||||||
|
source = "coder/coder"
|
||||||
|
version = ">= 2.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
# A built-in icon like "/icon/code.svg" or a full URL of icon
|
||||||
|
icon_url = "/icon/copyparty.svg"
|
||||||
|
# a map of all possible values
|
||||||
|
# options = {
|
||||||
|
# "Option 1" = {
|
||||||
|
# "name" = "Option 1",
|
||||||
|
# "value" = "1"
|
||||||
|
# "icon" = "/emojis/1.png"
|
||||||
|
# }
|
||||||
|
# "Option 2" = {
|
||||||
|
# "name" = "Option 2",
|
||||||
|
# "value" = "2"
|
||||||
|
# "icon" = "/emojis/2.png"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add required variables for your modules and remove any unneeded variables
|
||||||
|
variable "agent_id" {
|
||||||
|
type = string
|
||||||
|
description = "The ID of a Coder agent."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "log_path" {
|
||||||
|
type = string
|
||||||
|
description = "The path to log copyparty to."
|
||||||
|
default = "/tmp/copyparty.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "port" {
|
||||||
|
type = number
|
||||||
|
description = "ports to listen on (comma/range); ignored for unix-sockets (default: 3923)"
|
||||||
|
default = 3923
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "slug" {
|
||||||
|
type = string
|
||||||
|
description = "The slug for the copyparty application."
|
||||||
|
default = "copyparty"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "display_name" {
|
||||||
|
type = string
|
||||||
|
description = "The display name for the copyparty application."
|
||||||
|
default = "copyparty"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "group" {
|
||||||
|
type = string
|
||||||
|
description = "The name of a group that this app belongs to."
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "open_in" {
|
||||||
|
type = string
|
||||||
|
description = <<-EOT
|
||||||
|
Determines where the app will be opened. Valid values are `"tab"` and `"slim-window" (default)`.
|
||||||
|
`"tab"` opens in a new tab in the same browser window.
|
||||||
|
`"slim-window"` opens a new browser window without navigation controls.
|
||||||
|
EOT
|
||||||
|
default = "slim-window"
|
||||||
|
validation {
|
||||||
|
condition = contains(["tab", "slim-window"], var.open_in)
|
||||||
|
error_message = "The 'open_in' variable must be one of: 'tab', 'slim-window'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "subdomain" {
|
||||||
|
type = bool
|
||||||
|
description = <<-EOT
|
||||||
|
Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder.
|
||||||
|
If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible.
|
||||||
|
EOT
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "share" {
|
||||||
|
type = string
|
||||||
|
default = "owner"
|
||||||
|
validation {
|
||||||
|
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
|
||||||
|
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# variable "mutable" {
|
||||||
|
# type = bool
|
||||||
|
# description = "Whether the parameter is mutable."
|
||||||
|
# default = true
|
||||||
|
# }
|
||||||
|
|
||||||
|
variable "order" {
|
||||||
|
type = number
|
||||||
|
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
# Add other variables here
|
||||||
|
|
||||||
|
variable "pinned_version" {
|
||||||
|
type = string
|
||||||
|
description = "Install a specific version in semver format (v1.19.16)."
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "arguments" {
|
||||||
|
type = list(string)
|
||||||
|
description = "A list of arguments to pass to the application."
|
||||||
|
default = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
resource "coder_script" "copyparty" {
|
||||||
|
agent_id = var.agent_id
|
||||||
|
display_name = "copyparty"
|
||||||
|
icon = local.icon_url
|
||||||
|
script = templatefile("${path.module}/run.sh", {
|
||||||
|
LOG_PATH : var.log_path,
|
||||||
|
PORT : var.port,
|
||||||
|
PINNED_VERSION : var.pinned_version,
|
||||||
|
ARGUMENTS : join(",", var.arguments),
|
||||||
|
})
|
||||||
|
run_on_start = true
|
||||||
|
run_on_stop = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "coder_app" "copyparty" {
|
||||||
|
agent_id = var.agent_id
|
||||||
|
slug = var.slug
|
||||||
|
display_name = var.display_name
|
||||||
|
url = "http://localhost:${var.port}"
|
||||||
|
icon = local.icon_url
|
||||||
|
subdomain = var.subdomain
|
||||||
|
share = var.share
|
||||||
|
order = var.order
|
||||||
|
group = var.group
|
||||||
|
open_in = var.open_in
|
||||||
|
|
||||||
|
# Remove if the app does not have a healthcheck endpoint
|
||||||
|
healthcheck {
|
||||||
|
url = "http://localhost:${var.port}"
|
||||||
|
interval = 5
|
||||||
|
threshold = 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# data "coder_parameter" "copyparty" {
|
||||||
|
# type = "list(string)"
|
||||||
|
# name = "copyparty"
|
||||||
|
# display_name = "copyparty"
|
||||||
|
# icon = local.icon_url
|
||||||
|
# mutable = var.mutable
|
||||||
|
# default = local.options["Option 1"]["value"]
|
||||||
|
|
||||||
|
# dynamic "option" {
|
||||||
|
# for_each = local.options
|
||||||
|
# content {
|
||||||
|
# icon = option.value.icon
|
||||||
|
# name = option.value.name
|
||||||
|
# value = option.value.value
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
Executable
+100
@@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Convert templated variables to shell variables
|
||||||
|
# This variable is assigned to itself, so the assignment does nothing.
|
||||||
|
# shellcheck disable=SC2269
|
||||||
|
LOG_PATH="${LOG_PATH}"
|
||||||
|
|
||||||
|
# Ports to listen on (comma/range); ignored for unix-sockets (default: 3923)
|
||||||
|
PORT="${PORT}"
|
||||||
|
# Pinned version (e.g., v1.19.16); overrides latest release discovery if set
|
||||||
|
PINNED_VERSION="${PINNED_VERSION}"
|
||||||
|
# Custom CLI Arguments# The variable from Terraform is a single, comma-separated string.
|
||||||
|
# We need to split it into a proper bash array using the comma (,) as the delimiter.
|
||||||
|
IFS=',' read -r -a ARGUMENTS <<< "${ARGUMENTS}"
|
||||||
|
|
||||||
|
# VARIABLE appears unused. Verify use (or export if used externally).
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
MODULE_NAME="Copyparty"
|
||||||
|
|
||||||
|
# VARIABLE appears unused. Verify use (or export if used externally).
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
BOLD='\033[0;1m'
|
||||||
|
|
||||||
|
printf '%sInstalling %s ...\n\n' "$${BOLD}" "$${MODULE_NAME}"
|
||||||
|
|
||||||
|
# Add code here
|
||||||
|
# Use variables from the templatefile function in main.tf
|
||||||
|
# e.g. LOG_PATH, PORT, etc.
|
||||||
|
|
||||||
|
printf "🐍 Verifying Python 3 installation...\n"
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
printf "❌ Python3 could not be found. Please install it to continue.\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf "✅ Python3 is installed.\n\n"
|
||||||
|
|
||||||
|
RELEASE_TO_INSTALL=""
|
||||||
|
# Install provided version to pin, otherwise discover latest github release from `https://github.com/9001/copyparty`.
|
||||||
|
if [[ -n "$${PINNED_VERSION}" ]]; then
|
||||||
|
printf "📌 Pinned version specified: %s\n" "$${PINNED_VERSION}"
|
||||||
|
# Verify that it is in v#.#.# format
|
||||||
|
if [[ ! "$${PINNED_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
printf "❌ Invalid format for PINNED_VERSION. Expected 'v#.#.#' (e.g., v1.19.16).\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
RELEASE_TO_INSTALL="$${PINNED_VERSION}"
|
||||||
|
printf "✅ Using pinned version %s.\n\n" "$${RELEASE_TO_INSTALL}"
|
||||||
|
else
|
||||||
|
printf "🔎 Discovering latest release from GitHub...\n"
|
||||||
|
# Use curl to get the latest release tag from the GitHub API and sed to parse it
|
||||||
|
LATEST_RELEASE=$(curl -fsSL https://api.github.com/repos/9001/copyparty/releases/latest | grep '"tag_name":' | sed -E 's/.*"(v[^"]+)".*/\1/')
|
||||||
|
if [[ -z "$${LATEST_RELEASE}" ]]; then
|
||||||
|
printf "❌ Could not determine the latest release. Please check your internet connection.\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
RELEASE_TO_INSTALL="$${LATEST_RELEASE}"
|
||||||
|
printf "🏷️ Latest release is %s.\n\n" "$${RELEASE_TO_INSTALL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download appropriate release version assets: `copyparty-sfx.py` and `helptext.html`.
|
||||||
|
printf "🚀 Downloading copyparty v%s...\n" "$${RELEASE_TO_INSTALL}"
|
||||||
|
DOWNLOAD_URL="https://github.com/9001/copyparty/releases/download/$${RELEASE_TO_INSTALL}"
|
||||||
|
|
||||||
|
printf "⏬ Downloading copyparty-sfx.py...\n"
|
||||||
|
if ! curl -fsSL -o /tmp/copyparty-sfx.py "$${DOWNLOAD_URL}/copyparty-sfx.py"; then
|
||||||
|
printf "❌ Failed to download copyparty-sfx.py.\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "⏬ Downloading helptext.html...\n"
|
||||||
|
if ! curl -fsSL -o /tmp/helptext.html "$${DOWNLOAD_URL}/helptext.html"; then
|
||||||
|
# This is not a fatal error, just a warning.
|
||||||
|
printf "⚠️ Could not download helptext.html. The application will still work.\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x /tmp/copyparty-sfx.py
|
||||||
|
printf "✅ Download complete.\n\n"
|
||||||
|
|
||||||
|
printf "🥳 Installation complete!\n\n"
|
||||||
|
|
||||||
|
# Build a clean, quoted string of the command for logging purposes only.
|
||||||
|
log_command="python3 /tmp/copyparty-sfx.py -p '$${PORT}'"
|
||||||
|
for arg in "$${ARGUMENTS[@]}"; do
|
||||||
|
# printf "DEBUG: ARG [$${arg}]\n"
|
||||||
|
log_command+=" '$${arg}'"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clear the log file and write the header and command string using printf.
|
||||||
|
{
|
||||||
|
printf "=== Starting copyparty at %s ===\n" "$(date)"
|
||||||
|
printf "EXECUTING: %s\n" "$${log_command}"
|
||||||
|
} > "$${LOG_PATH}"
|
||||||
|
|
||||||
|
printf "👷 Starting %s in background...\n\n" "$${MODULE_NAME}"
|
||||||
|
|
||||||
|
# Execute the actual command using the robust array expansion.
|
||||||
|
# Then, append its output (stdout and stderr) to the log file.
|
||||||
|
python3 /tmp/copyparty-sfx.py -p "$${PORT}" "$${ARGUMENTS[@]}" >> "$${LOG_PATH}" 2>&1 &
|
||||||
|
|
||||||
|
printf "✅ Service started. Check logs at %s\n\n" "$${LOG_PATH}"
|
||||||
Reference in New Issue
Block a user