Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

WORKDIR /app

COPY . .

RUN pip install --no-cache-dir .

CMD ["pqcli", "--basic"]
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,43 @@ $ cd pq-cli
$ pip install --user .
```

## Docker / Docker Compose

The repository includes a `Dockerfile` and `docker-compose.yml` that run the
game with `--basic` and keep save data in a persistent named volume.

There is no registry pipeline required for this setup: build locally, then run
with Compose.

```console
# Build image locally from the Dockerfile
docker compose build

# First run (interactive): create your character in the shared save volume
docker compose run --rm pqcli-init

# Later runs on a server (detached, default save slot 1):
docker compose up -d pqcli

# List existing saves in the same volume:
docker compose run --rm pqcli-init pqcli --basic --list-saves

# Change detached slot if needed (example: slot 2):
PQCLI_SAVE_SLOT=2 docker compose up -d pqcli

# Follow logs / stop detached container:
docker compose logs -f pqcli
docker compose stop pqcli
```

On first run, if no save data exists in the mounted volume, `pqcli` will
automatically launch an interactive character-creation bootstrap in CLI mode
before starting the normal interface.

For server usage, run first-time setup without a forced slot (`pqcli-init`),
then run the long-lived detached service (`pqcli`) that loads slot `1` by
default.

## Contributing

```sh
Expand Down
26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
services:
pqcli-init:
build: .
image: ghcr.io/${GITHUB_REPOSITORY_OWNER:-local}/pq-cli:latest
stdin_open: true
tty: true
command: ["pqcli", "--basic"]
environment:
XDG_CONFIG_HOME: /data
volumes:
- pqcli-data:/data

pqcli:
build: .
stdin_open: true
tty: true
stop_signal: SIGINT
stop_grace_period: 30s
command: ["sh", "-c", "pqcli --basic --load-save ${PQCLI_SAVE_SLOT:-1}"]
environment:
XDG_CONFIG_HOME: /data
volumes:
- pqcli-data:/data

volumes:
pqcli-data:
17 changes: 17 additions & 0 deletions pqcli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ def list_players(roster: Roster, file: T.Optional[T.Any] = None) -> None:
print(f"{i}. {player.name}", file=file)


def bootstrap_first_run(roster: Roster, args: argparse.Namespace) -> None:
if roster.players or args.list_saves or args.load_save:
return

print("No saved characters found. Starting first-run character creation.")
ui = BasicUserInterface(roster, None, args)
while not roster.players:
player = ui.create_player(auto_play=False)
if player:
return
if not ui.confirm("No character created. Do you want to try again?"):
print("A character is required for first run.", file=sys.stderr)
raise SystemExit(1)


def main() -> None:
args = parse_args()
roster = Roster.load(SAVE_PATH)
Expand All @@ -83,6 +98,8 @@ def main() -> None:
list_players(roster, file=sys.stderr)
exit(1)

bootstrap_first_run(roster, args)

try:
ui = args.ui(roster, player, args)
ui.run()
Expand Down
6 changes: 4 additions & 2 deletions pqcli/ui/basic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def main_menu(self) -> None:
self.quit()
break

def create_player(self) -> T.Optional[Player]:
def create_player(self, *, auto_play: bool = True) -> T.Optional[Player]:
name = input("Name your new character: ")
if not name:
print("Cancelled.")
Expand All @@ -123,7 +123,9 @@ def create_player(self) -> T.Optional[Player]:
name=name, race=race, class_=class_, stats=stats
)
self.roster.players.append(player)
if self.confirm("Do you want to play as your new character?"):
if auto_play and self.confirm(
"Do you want to play as your new character?"
):
self.play(player)
return player

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies = [
"xdg-base-dirs>=6.0.0,<7.0.0",
"urwid>=2.1.2,<3.0.0",
"urwid-readline>=0.13,<1.0.0",
"pre-commit>=4.5.0",
]

[project.optional-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.