Skip to content

Commit

Permalink
Add palworld (#81)
Browse files Browse the repository at this point in the history
* add palworld

* default start period

* do

* no probe

* update lib

* adapt to lib changes

* update lib

* adapt to lib changes

* update lib

* adapt to lib changes

* adapt to lib changes

* update lib

* update lib

* update lib

* update lib

* update lib

* fmt

* update readme
  • Loading branch information
stavros-k authored Aug 2, 2024
1 parent c910dc6 commit e954a66
Show file tree
Hide file tree
Showing 30 changed files with 2,242 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Thank you for your understanding.
| omada-controller | community || - |
| organizr | community || - |
| overseerr | community || - |
| palworld | community | - | - |
| palworld | community | | - |
| paperless-ngx | community | - | - |
| passbolt | community | - | - |
| pgadmin | community | - | - |
Expand Down
1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ words:
- syncthing
- tailscale
- tailscaled
- palworld
- photoprism
- tensorchord
- organizr
Expand Down
3 changes: 3 additions & 0 deletions ix-dev/community/palworld/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Palworld

[Palworld](https://www.pocketpair.jp/palworld) is a multiplayer, open-world survival crafting game where you befriend and collect mysterious creatures called "Pals".
50 changes: 50 additions & 0 deletions ix-dev/community/palworld/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
app_version: palworld
capabilities:
- description: Palworld is able to change file ownership.
name: CHOWN
- description: Palworld is able to bypass file read, write, and execute permission
checks.
name: DAC_OVERRIDE
- description: Palworld is able to bypass permission checks on operations that normally
require the file system UID of the process to match the UID of the file.
name: FOWNER
- description: Palworld is able to set the setuid attribute on a file.
name: SETUID
- description: Palworld is able to set the setgid attribute on a file.
name: SETGID
- description: Palworld is able to override resource limits.
name: SYS_RESOURCE
- description: Palworld is able to send signals to processes owned by others.
name: KILL
- description: Palworld is able to bind to privileged ports.
name: NET_BIND_SERVICE
categories:
- games
description: Palworld is a multiplayer, open-world survival crafting game where you
befriend and collect mysterious creatures called "Pals".
home: https://www.pocketpair.jp/palworld
host_mounts: []
icon: https://media.sys.truenas.net/apps/palworld/icons/icon.webp
keywords:
- game
- palworld
lib_version: 1.0.0
lib_version_hash: 66c98111180da566a3bcc9ee1d1be4f673356f453b5d97ee7c784c9a38ee9999
maintainers:
- email: [email protected]
name: truenas
url: https://www.truenas.com/
name: palworld
run_as_context:
- description: Palworld runs as root user.
gid: 0
group_name: root
uid: 0
user_name: root
screenshots: []
sources:
- https://www.pocketpair.jp/palworld
- https://github.com/ich777/docker-steamcmd-server/tree/palworld
title: Palworld
train: community
version: 1.0.0
7 changes: 7 additions & 0 deletions ix-dev/community/palworld/item.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
categories:
- games
icon_url: https://media.sys.truenas.net/apps/palworld/icons/icon.webp
screenshots: []
tags:
- game
- palworld
17 changes: 17 additions & 0 deletions ix-dev/community/palworld/ix_values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
images:
image:
repository: ich777/steamcmd
tag: palworld
consts:
init_container_name: init
palworld_container_name: palworld
steamcmd_path: /serverdata/steamcmd
server_path: /serverdata/serverfiles
reserved_keys:
- RCONEnabled
- RCONPort
- PublicPort
- AdminPassword
- ServerName
- ServerDescription
- ServerPassword
Empty file.
27 changes: 27 additions & 0 deletions ix-dev/community/palworld/migrations/migration_helpers/cpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import math
import re
import os

CPU_COUNT = os.cpu_count()

NUMBER_REGEX = re.compile(r"^[1-9][0-9]$")
FLOAT_REGEX = re.compile(r"^[0-9]+\.[0-9]+$")
MILI_CPU_REGEX = re.compile(r"^[0-9]+m$")


def transform_cpu(cpu) -> int:
result = 2
if NUMBER_REGEX.match(cpu):
result = int(cpu)
elif FLOAT_REGEX.match(cpu):
result = int(math.ceil(float(cpu)))
elif MILI_CPU_REGEX.match(cpu):
num = int(cpu[:-1])
num = num / 1000
result = int(math.ceil(num))

if CPU_COUNT is not None:
# Do not exceed the actual CPU count
result = min(result, CPU_COUNT)

return result
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def migrate_dns_config(dns_config):
if not dns_config:
return []

dns_opts = []
for opt in dns_config.get("options", []):
dns_opts.append(f"{opt['name']}:{opt['value']}")

return dns_opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
def get_value_from_secret(secrets={}, secret_name="", key=""):
if not secrets or not secret_name or not key:
raise ValueError("Expected [secrets], [secret_name] and [key] to be set")
for secret in secrets.items():
curr_secret_name = secret[0]
curr_data = secret[1]

if curr_secret_name.endswith(secret_name):
if not curr_data.get(key, None):
raise ValueError(
f"Expected [{key}] to be set in secret [{curr_secret_name}]"
)
return curr_data[key]

raise ValueError(f"Secret [{secret_name}] not found")
49 changes: 49 additions & 0 deletions ix-dev/community/palworld/migrations/migration_helpers/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import re
import math
import psutil

TOTAL_MEM = psutil.virtual_memory().total

SINGLE_SUFFIX_REGEX = re.compile(r"^[1-9][0-9]*([EPTGMK])$")
DOUBLE_SUFFIX_REGEX = re.compile(r"^[1-9][0-9]*([EPTGMK])i$")
BYTES_INTEGER_REGEX = re.compile(r"^[1-9][0-9]*$")
EXPONENT_REGEX = re.compile(r"^[1-9][0-9]*e[0-9]+$")

SUFFIX_MULTIPLIERS = {
"K": 10**3,
"M": 10**6,
"G": 10**9,
"T": 10**12,
"P": 10**15,
"E": 10**18,
}

DOUBLE_SUFFIX_MULTIPLIERS = {
"Ki": 2**10,
"Mi": 2**20,
"Gi": 2**30,
"Ti": 2**40,
"Pi": 2**50,
"Ei": 2**60,
}


def transform_memory(memory):
result = 4096 # Default to 4GB

if re.match(SINGLE_SUFFIX_REGEX, memory):
suffix = memory[-1]
result = int(memory[:-1]) * SUFFIX_MULTIPLIERS[suffix]
elif re.match(DOUBLE_SUFFIX_REGEX, memory):
suffix = memory[-2:]
result = int(memory[:-2]) * DOUBLE_SUFFIX_MULTIPLIERS[suffix]
elif re.match(BYTES_INTEGER_REGEX, memory):
result = int(memory)
elif re.match(EXPONENT_REGEX, memory):
result = int(float(memory))

result = math.ceil(result)
result = min(result, TOTAL_MEM)
# Convert to Megabytes
result = result / 1024 / 1024
return int(result)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from .memory import transform_memory, TOTAL_MEM
from .cpu import transform_cpu, CPU_COUNT


def migrate_resources(resources, gpus=None, system_gpus=None):
gpus = gpus or {}
system_gpus = system_gpus or []

result = {
"limits": {
"cpus": (CPU_COUNT or 2) / 2,
"memory": {TOTAL_MEM / 1024 / 1024},
}
}

if resources.get("limits", {}).get("cpu", ""):
result["limits"].update(
{"cpus": transform_cpu(resources.get("limits", {}).get("cpu", ""))}
)
if resources.get("limits", {}).get("memory", ""):
result["limits"].update(
{"memory": transform_memory(resources.get("limits", {}).get("memory", ""))}
)

gpus_result = {}
for gpu in gpus.items() if gpus else []:
kind = gpu[0].lower() # Kind of gpu (amd, nvidia, intel)
count = gpu[1] # Number of gpus user requested

if count == 0:
continue

if "amd" in kind or "intel" in kind:
gpus_result.update({"use_all_gpus": True})
elif "nvidia" in kind:
sys_gpus = [
gpu_item
for gpu_item in system_gpus
if gpu_item.get("error") is None
and gpu_item.get("vendor", None) is not None
and gpu_item.get("vendor", "").upper() == "NVIDIA"
]
for sys_gpu in sys_gpus:
if count == 0: # We passed # of gpus that user previously requested
break
guid = sys_gpu.get("vendor_specific_config", {}).get("uuid", "")
pci_slot = sys_gpu.get("pci_slot", "")
if not guid or not pci_slot:
continue

gpus_result.update(
{"nvidia_gpu_selection": {pci_slot: {"uuid": guid, "use_gpu": True}}}
)
count -= 1

if gpus_result:
result.update({"gpus": gpus_result})

return result
115 changes: 115 additions & 0 deletions ix-dev/community/palworld/migrations/migration_helpers/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
def migrate_storage_item(storage_item, include_read_only=False):
if not storage_item:
raise ValueError("Expected [storage_item] to be set")

result = {}
if storage_item["type"] == "ixVolume":
result = migrate_ix_volume_type(storage_item)
elif storage_item["type"] == "hostPath":
result = migrate_host_path_type(storage_item)
elif storage_item["type"] == "emptyDir":
result = migrate_empty_dir_type(storage_item)
elif storage_item["type"] == "smb-pv-pvc":
result = migrate_smb_pv_pvc_type(storage_item)

mount_path = storage_item.get("mountPath", "")
if mount_path:
result.update({"mount_path": mount_path})

if include_read_only:
result.update({"read_only": storage_item.get("readOnly", False)})
return result


def migrate_smb_pv_pvc_type(smb_pv_pvc):
smb_config = smb_pv_pvc.get("smbConfig", {})
if not smb_config:
raise ValueError("Expected [smb_pv_pvc] to have [smbConfig] set")

return {
"type": "cifs",
"cifs_config": {
"server": smb_config["server"],
"share": smb_config["share"],
"domain": smb_config.get("domain", ""),
"username": smb_config["username"],
"password": smb_config["password"],
},
}


def migrate_empty_dir_type(empty_dir):
empty_dir_config = empty_dir.get("emptyDirConfig", {})
if not empty_dir_config:
raise ValueError("Expected [empty_dir] to have [emptyDirConfig] set")

if empty_dir_config.get("medium", "") == "Memory":
# Convert Gi to Mi
size = empty_dir_config.get("size", 0.5) * 1024
return {
"type": "tmpfs",
"tmpfs_config": {"size": size},
}

return {"type": "temporary"}


def migrate_ix_volume_type(ix_volume):
vol_config = ix_volume.get("ixVolumeConfig", {})
if not vol_config:
raise ValueError("Expected [ix_volume] to have [ixVolumeConfig] set")

result = {
"type": "ix_volume",
"ix_volume_config": {
"acl_enable": vol_config.get("aclEnable", False),
"dataset_name": vol_config.get("datasetName", ""),
},
}

if vol_config.get("aclEnable", False):
result["ix_volume_config"].update(
{"acl_entries": migrate_acl_entries(vol_config["aclEntries"])}
)

return result


def migrate_host_path_type(host_path):
path_config = host_path.get("hostPathConfig", {})
if not path_config:
raise ValueError("Expected [host_path] to have [hostPathConfig] set")

result = {
"type": "host_path",
"host_path_config": {
"acl_enable": path_config.get("aclEnable", False),
},
}

if path_config.get("aclEnable", False):
result["host_path_config"].update(
{"acl": migrate_acl_entries(path_config.get("acl", {}))}
)
else:
result["host_path_config"].update({"path": path_config["hostPath"]})

return result


def migrate_acl_entries(acl_entries: dict) -> dict:
entries = []
for entry in acl_entries.get("entries", []):
entries.append(
{
"access": entry["access"],
"id": entry["id"],
"id_type": entry["id_type"],
}
)

return {
"entries": entries,
"options": {"force": acl_entries.get("force", False)},
"path": acl_entries["path"],
}
Loading

0 comments on commit e954a66

Please sign in to comment.