Unverified Commit 587f1831 authored by Minseong Choi's avatar Minseong Choi 💬 Committed by GitHub
Browse files

Merge pull request #19 from MliroLirrorsIngenuity/chore/issue-sweep

chore: work the tracker items that need no cluster
parents 5d4f3063 503240db
Loading
Loading
Loading
Loading

.gitattributes

0 → 100644
+8 −0
Changes for .gitattributes: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Everything in this repository is consumed on Linux: deploy/bootstrap.sh is embedded
# verbatim by bootstrap_asset.go and piped into `bash -s`, the Dockerfiles and entrypoints
# are read inside the images, and the operator ships YAML. Without eol=lf a Windows
# checkout under core.autocrlf=true hands all of them CRs.
* text=auto eol=lf

# The gradle wrappers' Windows launchers are the one thing that wants CRLF.
*.bat text eol=crlf
+94 −0
Changes for .github/workflows/ci.yml: 94 added lines, 0 removed lines.
Original line number Diff line number Diff line
# Runs the checks on pull requests and on main.
#
# release.yml already runs `go vet` and `go test`, but only once a vX.Y.Z tag exists — by then
# a red change is on the release path and the only remedy is a new tag. This is the same gate
# moved to where it can still stop something, plus the panel suite, which nothing ran at all:
# the release goes through the Dockerfile, and the Dockerfile runs `npm run build`, never
# `npm test`.
#
# The push trigger is limited to main rather than every branch, for the reason release.yml is
# not repeated here: a branch with an open PR would otherwise run the whole suite twice per
# push, once for refs/heads/<branch> and once for refs/pull/N/merge. Those are different
# concurrency groups, so neither cancels the other, and this repository is private and billed
# for both. A branch with no PR open yet is the one case that loses coverage, and opening the
# PR is what asks for the answer.
name: ci

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  go:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod

      - run: go vet ./...
      - run: go test ./...

  shell:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # bootstrap.sh is the only thing that ever runs on a fresh host, and nothing here can
      # run it — it wants root, a package manager and k3s. Syntax plus the extracted-block
      # checks in bootstrap_test.sh is the coverage that is reachable without a machine.
      # Each file is parsed by the interpreter its own shebang names. A blanket `sh -n` is
      # wrong and not obviously so: on a developer machine `sh` is usually bash and passes
      # everything, while the runner's `sh` is dash and rejects bootstrap.sh at the first of
      # its arrays. Honouring the shebang is what makes local and CI agree.
      - name: Check shell syntax
        run: |
          for f in $(git ls-files '*.sh'); do
            case "$(head -1 "$f")" in
              *bash) bash -n "$f" || exit 1 ;;
              *) sh -n "$f" || exit 1 ;;
            esac
          done

      - run: sh deploy/bootstrap_test.sh

  panel:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # The Dockerfile's `FROM node:<major>` is the only place the panel's Node version is
      # declared — there is no .nvmrc and no engines field. Reading it here rather than
      # repeating the number keeps CI testing the version a release is actually built on;
      # a second copy would drift silently the first time the image is bumped.
      - name: Read the panel's Node version from the Dockerfile
        id: node
        run: |
          version="$(sed -n 's/^FROM.*node:\([0-9][0-9]*\)-.*/\1/p' Dockerfile | head -1)"
          [ -n "$version" ] || { echo "Dockerfile has no 'FROM ... node:<major>' line"; exit 1; }
          echo "version=${version}" >> "$GITHUB_OUTPUT"

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ steps.node.outputs.version }}
          cache: npm
          cache-dependency-path: panel/package-lock.json

      - run: npm ci
        working-directory: panel

      - run: npm test
        working-directory: panel

      - run: npm run typecheck
        working-directory: panel
+9 −3
Changes for bootstrap_asset_test.go: 9 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -73,9 +73,15 @@ func TestLobbyLuckPermsWiringIsConsistent(t *testing.T) {
// default it can inherit — and nothing else in the install would notice it missing. The failure
// surfaces only when a legacy player joins, on a host that installed cleanly.
func TestBootstrapPinsViaBlockConnectionsOff(t *testing.T) {
	// go:embed takes the working tree verbatim, and this repository pins no eol attribute, so
	// a Windows checkout embeds CRLF. Only the assertion spanning a line break below cares.
	script := strings.ReplaceAll(BootstrapScript(), "\r\n", "\n")
	// go:embed takes the working tree verbatim, so the eol attribute is what keeps a Windows
	// checkout from compiling CRs into the installer. Assert it rather than normalizing them
	// away: the only assertion that would otherwise notice is the one spanning a line break
	// below, and it would report a missing pin instead of the line endings.
	script := BootstrapScript()
	if strings.Contains(script, "\r\n") {
		t.Fatal("embedded bootstrap.sh has CRLF line endings; .gitattributes pins *.sh to LF " +
			"and this script is piped into `bash -s` on a Linux host")
	}

	const key = "serverside-blockconnections"
	if !strings.Contains(script, key+": false") {
+10 −2
Changes for cmd/felis/nano.go: 10 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -13,8 +13,16 @@ package main
// off-loopback with -listen; see the flag below for why that is an explicit opt-in.
//
// This is the no-database delivery of the identical brain `felis api` mounts through its
// route table (internal/api.HasJoinedHandler). `felis setup --nano` / the bootstrap nano
// choice install this as the runtime service; here it just serves.
// route table (internal/api.HasJoinedHandler). The bootstrap installer's nano choice — its
// `[2] Felis-nano` prompt, or FELIS_INSTALL_MODE=nano — installs this as the
// felis-nano.service unit; here it just serves.
//
// There is deliberately no `felis setup --nano`. setup re-images the host through the full
// bootstrap TUI and carries no install-mode parameter anywhere (nothing in Go reads or sets
// FELIS_INSTALL_MODE), so such a flag would either re-run the installer — which is what
// pointing at the installer already does — or tear a full install down into a nano one,
// which is an uninstall, not a flag. This is the same reasoning that makes setup's --dev
// refuse and name the installer rather than pretend to choose a channel.

import (
	"context"
+62 −8
Changes for deploy/bootstrap.sh: 62 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -30,6 +30,14 @@
#   FELIS_INSTALL_MODE full|nano — skip the prompt (default: ask on a tty, else full)
#   FELIS_NANO_LISTEN listen addr for `felis nano` (default: 127.0.0.1:8081 — loopback
#                     only; set a private-network IP to serve an off-host proxy)
#   FELIS_LEGACY_FORWARDING_SERVERS comma-separated backends that receive their identity
#                     through the handshake address instead of modern forwarding
#                     (default: legacy18). Read once at Velocity start, so changing it
#                     means re-running this script and restarting the proxy.
#   FELIS_VELOCITY_FORK_JAR path to a Felis-Legacy Velocity fork build to install as the
#                     proxy instead of the stock download (default: unset, stock).
#   FELIS_VELOCITY_FORK_JAR_SHA256 expected sha256 of that jar. REQUIRED whenever the jar
#                     above is set; the install refuses on a mismatch.
#   FELIS_GO_VERSION  Go toolchain used to build the nano binary (default: 1.26.4)
#   FELIS_REPO_URL    git URL to build from   (raw script mode only)
#   FELIS_VERSION_BOOTSTRAP release|dev — which version to install (default: release).
@@ -95,6 +103,11 @@ INSTALL_MODE="${FELIS_INSTALL_MODE:-}"
# own players stop getting in. Same-host Velocity reaches 127.0.0.1 fine; a proxy on
# another machine must opt in explicitly with FELIS_NANO_LISTEN=<private-ip>:8081.
FELIS_NANO_LISTEN="${FELIS_NANO_LISTEN:-127.0.0.1:8081}"
# Backends that take their forwarded identity through the handshake address instead of
# proxy-wide modern forwarding. See write_velocity_service for why a protocol-47 backend
# needs this. Overridable because adding a second 1.8 backend otherwise means editing this
# script; it is still a restart-time list, not one that follows the CRs.
FELIS_LEGACY_FORWARDING_SERVERS="${FELIS_LEGACY_FORWARDING_SERVERS:-legacy18}"
FELIS_GO_VERSION="${FELIS_GO_VERSION:-1.26.4}"
PKG_LOCK_TIMEOUT="${PKG_LOCK_TIMEOUT:-${APT_LOCK_TIMEOUT:-900}}"
APT_LOCK_TIMEOUT="${APT_LOCK_TIMEOUT:-$PKG_LOCK_TIMEOUT}"
@@ -123,9 +136,20 @@ FELIS_VELOCITY_VERSION="${FELIS_VELOCITY_VERSION:-3.5.1}"
#
# Opt-in because it is unmeasured where it counts: FL-008's probe runs offline-mode
# against a stub, and this jar would carry every real Mojang session on the server.
# The build lives in Felis-Legacy and is not byte-reproducible, so there is no digest
# to pin here — the jar is trusted because that probe certified the build.
FELIS_VELOCITY_FORK_JAR="${FELIS_VELOCITY_FORK_JAR:-}"
# Expected sha256 of that jar, REQUIRED whenever it is set. Case and internal spaces are
# ignored, so whatever sha256sum, Get-FileHash or certutil printed can be pasted as-is.
# No digest is hardcoded here:
# the build lives in Felis-Legacy and has never been reproduced on a second machine, so
# any constant this script carried would pin one machine's output rather than the fork.
#
# So this is not a supply-chain signature and does not pretend to be one — an operator
# who can write the jar can write this value too. What it does buy: a path is not an
# identity, and every re-run of this script re-checks it. A truncated copy, a stale build
# left at the same path, or the two-patch jar where the three-patch one was meant all
# change the digest and stop the install. Naming the digest once is what turns "whatever
# is at that path today" into one specific build.
FELIS_VELOCITY_FORK_JAR_SHA256="${FELIS_VELOCITY_FORK_JAR_SHA256:-}"
# Temurin 25: Velocity 3.5 needs 21+, and 25 is also what a future Velocity 4 requires,
# so the runtime does not have to move again when the pin does. Distro JDK packaging is
# a lottery across four package managers — a tarball is one code path everywhere (same
@@ -1462,12 +1486,31 @@ install_jre() {

install_velocity() {
  install_jre
  local url tmp
  local url tmp have want
  prepare_velocity_layout
  if [ -n "$FELIS_VELOCITY_FORK_JAR" ]; then
    [ -f "$FELIS_VELOCITY_FORK_JAR" ] \
      || die "FELIS_VELOCITY_FORK_JAR is not a readable file: ${FELIS_VELOCITY_FORK_JAR}"
    log "installing the Felis-Legacy Velocity fork from ${FELIS_VELOCITY_FORK_JAR}"
    # Hash stdin, never the path — same reason as install_via_plugins: sha256sum escapes its
    # output line for a filename carrying a backslash or a newline, and the leading "\" that
    # adds would fail every comparison below.
    have="$(sha256sum <"$FELIS_VELOCITY_FORK_JAR" | cut -d' ' -f1)"
    # Refuse rather than warn. This jar is the proxy every player connects through, and a
    # warning in an install log is not a gate. The digest is printed so the first run after
    # a deliberate rebuild is one copy-paste, not an investigation.
    [ -n "$FELIS_VELOCITY_FORK_JAR_SHA256" ] || die \
      "FELIS_VELOCITY_FORK_JAR_SHA256 is required whenever FELIS_VELOCITY_FORK_JAR is set.
   The jar at that path hashes to ${have}.
   Check that against the build you meant to install, then re-run with
   FELIS_VELOCITY_FORK_JAR_SHA256=${have}"
    # Normalise the operator's digest before comparing. sha256sum prints lowercase, but the
    # build host is often Windows, where Get-FileHash prints uppercase and certutil has
    # shipped both with and without spaces between the bytes. All three name the same jar,
    # so comparing raw would refuse two of the three spellings and word it as tampering.
    want="$(printf '%s' "$FELIS_VELOCITY_FORK_JAR_SHA256" | tr -d '[:space:]' | tr 'A-Z' 'a-z')"
    [ "$have" = "$want" ] || die \
      "FELIS_VELOCITY_FORK_JAR checksum mismatch: got ${have}, expected ${want}"
    log "installing the Felis-Legacy Velocity fork from ${FELIS_VELOCITY_FORK_JAR} (sha256 ${have})"
    atomic_install_file "$FELIS_VELOCITY_FORK_JAR" "${VELOCITY_DIR}/velocity.jar" 0644 root root
  else
    log "resolving the newest Velocity ${FELIS_VELOCITY_VERSION} build"
@@ -1591,9 +1634,20 @@ install_velocity_service() {
  # behind ViaVersion, which strips modern forwarding's login-plugin-message when it down-translates
  # the proxy->backend pipeline to protocol 47; only the handshake field survives Via. The Felis
  # fork reads this list from -Dfelis.legacy-forwarding.servers and forwards those servers legacy;
  # every other backend keeps modern+secret untouched. v1 hardcodes the one legacy backend; the
  # upgrade path is to have the operator render this list from the MinecraftServer CRs.
  local legacy_forwarding_servers="legacy18"
  # every other backend keeps modern+secret untouched.
  #
  # The list is a JVM system property, so it is fixed for the life of the proxy process and a
  # change needs a Velocity restart. FELIS_LEGACY_FORWARDING_SERVERS makes that reachable
  # without editing this script, which is as far as a startup property can go. Having it follow
  # the MinecraftServer CRs instead is a larger change: the forwarding decision lives in the
  # fork's patch to Velocity core, not in the Felis plugin, so core would need to read state the
  # plugin owns and refreshes.
  #
  # The -D below is double-quoted in ExecStart on purpose. The fork trims each element, so it
  # accepts "legacy18, legacy112", but systemd splits ExecStart on whitespace before java ever
  # sees it -- unquoted, that spelling would hand java a stray "legacy112" argument and the unit
  # would not start. Quoting keeps the whole property one argv item.
  local legacy_forwarding_servers="${FELIS_LEGACY_FORWARDING_SERVERS}"
  cat > "$VELOCITY_SERVICE" <<EOF
[Unit]
Description=Felis Velocity proxy (Mojang authentication + modern forwarding)
@@ -1605,7 +1659,7 @@ Type=simple
User=${VELOCITY_USER}
Group=${VELOCITY_USER}
WorkingDirectory=${VELOCITY_DIR}
ExecStart=${JRE_DIR}/bin/java -Xms512M -Xmx1G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -Dmojang.sessionserver=http://${api_ip}:8081/session/minecraft/hasJoined -Dfelis.legacy-forwarding.servers=${legacy_forwarding_servers} -jar ${VELOCITY_DIR}/velocity.jar
ExecStart=${JRE_DIR}/bin/java -Xms512M -Xmx1G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -Dmojang.sessionserver=http://${api_ip}:8081/session/minecraft/hasJoined "-Dfelis.legacy-forwarding.servers=${legacy_forwarding_servers}" -jar ${VELOCITY_DIR}/velocity.jar
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
Loading