# Manual — Paquetes de Minecraft

`https://minecraft.sinul.es`

Sitio propio para guardar **resource packs, mods, plugins, datapacks, modpacks,
shaders y mundos**, y repartirlos por un enlace directo: tanto a la gente como a
los servidores de Minecraft, que se bajan los packs solos a partir de una URL.

---

## 1. Las dos pantallas

### Catálogo — `https://minecraft.sinul.es/`

Todo lo subido, lo más nuevo arriba. Arriba del todo hay:

- una **barra de búsqueda**, que mira en el nombre, la descripción, el nombre del
  fichero original y la categoría;
- un **filtro por categoría**.

Se pueden usar los dos a la vez (por ejemplo, "texturas" dentro de `Resource pack`).

Cada tarjeta muestra el nombre, la categoría, el tamaño, el fichero original, la
fecha y cuántas veces se ha descargado, y tiene tres botones:

| Botón | Qué hace |
| --- | --- |
| **⬇️ Descargar** | Baja el fichero. |
| **🔗 Copiar enlace** | Copia la URL completa al portapapeles. |
| **Borrar** | Borra la entrada y el fichero. Pide confirmación. **No se puede deshacer.** |

Debajo hay un desplegable, **"Datos para server.properties"**, explicado en el punto 3.

### Subir — `https://minecraft.sinul.es/upload/`

Formulario con cuatro cosas:

1. **Fichero** — se arrastra encima del recuadro o se pulsa para elegirlo.
2. **Nombre** — se rellena solo con el nombre del fichero, pero se puede cambiar.
   Es lo que se ve en el catálogo y de lo que sale la URL.
3. **Categoría** — de la lista, o `➕ Categoría nueva…` para inventarse una.
4. **Descripción** — opcional. Buen sitio para la versión de Minecraft, para qué
   servidor es o qué incluye.

Al darle a **Subir** aparece una barra de progreso (los modpacks tardan) y, al
terminar, el bloque con la URL y el SHA-1 ya listos para copiar.

---

## 2. Cómo se forman las URLs

El enlace sale del **nombre**, no del fichero:

```
Nombre:  Pack del server
URL:     https://minecraft.sinul.es/d/pack-del-server.zip
```

Se pasa a minúsculas, se quitan acentos y los espacios pasan a guiones.

**Subir con un nombre que ya existe reemplaza el paquete**, no crea uno nuevo. Se
conservan la URL, la fecha de alta y el contador de descargas; se sustituyen el
fichero, el tamaño y el SHA-1. El formulario avisa en cuanto escribes un nombre
que ya está cogido.

Tres cosas importantes:

- **La URL no cambia** ni al editar los datos ni al reemplazar el fichero. Un
  enlace ya repartido a los jugadores, o metido en un `server.properties`, sigue
  apuntando al sitio correcto.
- **Al reemplazar, el SHA-1 cambia** (contenido distinto, huella distinta). Hay
  que actualizar la línea `resource-pack-sha1` del servidor, o los clientes
  rechazarán el pack nuevo. Ver el punto 3.
- **Borrar sí rompe el enlace.** Si un servidor lo estaba usando, los jugadores
  dejarán de recibir el pack.

Si quieres saber la URL de algo, cópiala con el botón; no la escribas a mano.

---

## 3. Usarlo en un servidor de Minecraft

Esto es para lo que está hecho el sitio. En el `server.properties` del servidor:

```properties
resource-pack=https://minecraft.sinul.es/d/pack-del-server.zip
resource-pack-sha1=6802cc4c72c48fae414cbdc932447829f284ed11
```

Las dos líneas se copian con un botón desde el desplegable **"Datos para
server.properties"** de cada paquete. Después hay que **reiniciar el servidor**
para que las lea.

Opcionalmente, para que sea obligatorio aceptarlo:

```properties
require-resource-pack=true
```

### Qué es el SHA-1

Es una **huella dactilar del fichero**: una cadena de 40 caracteres que sale de
sus bytes. El mismo fichero da siempre la misma huella, y si cambia un solo byte
la huella sale completamente distinta.

El cliente de Minecraft la usa para dos cosas:

1. **Comprobar que la descarga llegó entera.** Baja el zip, calcula su huella y la
   compara. Si no cuadra, se cortó o se corrompió; las versiones modernas rechazan
   el pack.
2. **No volver a descargarlo.** Si el jugador ya tiene un pack con esa huella, se
   lo salta. **Sin la línea `resource-pack-sha1`, cada jugador se traga la descarga
   entera cada vez que entra al servidor.**

La huella se calcula sola al subir el fichero. No hay que hacer nada.

> Detalle: SHA-1 se considera inseguro para criptografía, pero aquí solo sirve para
> comprobar integridad y como clave de caché. Además es el algoritmo que impone
> Minecraft; no hay alternativa.

### Si cambias el contenido del pack

Sube el zip nuevo **con el mismo nombre**: reemplaza al anterior y la URL se
mantiene, así que la línea `resource-pack` del servidor no hay que tocarla.

Lo que **sí** hay que actualizar es la otra línea:

```properties
resource-pack-sha1=<el nuevo, cópialo de la web>
```

Contenido distinto, huella distinta. Si dejas la vieja, el cliente se bajará el
pack nuevo, verá que la huella no cuadra y lo rechazará. Y después, **reinicia el
servidor**.

Los jugadores se lo volverán a descargar (huella nueva, la caché del cliente ya no
sirve), que es justo lo que quieres cuando cambias las texturas.

---

## 4. Desde la línea de comandos

La web no hace nada especial: usa la misma API, así que todo se puede hacer con
`curl`.

### Subir y recibir las dos líneas de golpe

```bash
curl -s -X POST https://minecraft.sinul.es/api/packages \
  -F 'name=Pack del server' \
  -F 'category=resource-pack' \
  -F 'description=texturas de la temporada 3' \
  -F 'file=@pack.zip' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('resource-pack=%s' % d['absoluteUrl']); print('resource-pack-sha1=%s' % d['sha1'])"
```

Salida:

```
resource-pack=https://minecraft.sinul.es/d/pack-del-server.zip
resource-pack-sha1=6802cc4c72c48fae414cbdc932447829f284ed11
```

El campo `file` va **el último**: así el servidor ya tiene el nombre y la categoría
cuando empiezan a llegar los bytes.

Con `jq` instalado queda más corto:

```bash
... | jq -r '"resource-pack=\(.absoluteUrl)", "resource-pack-sha1=\(.sha1)"'
```

### Consultar algo ya subido

```bash
curl -s https://minecraft.sinul.es/api/packages/pack-del-server \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('resource-pack=%s' % d['absoluteUrl']); print('resource-pack-sha1=%s' % d['sha1'])"
```

### Solo el SHA-1

Viene en el JSON de la entrada, así que basta con pedirla:

```bash
curl -s https://minecraft.sinul.es/api/packages/pack-del-server \
| python3 -c "import json,sys; print(json.load(sys.stdin)['sha1'])"
```

La cabecera `X-Package-Sha1` de la descarga lleva ese mismo valor. Solo merece la
pena cuando tienes la URL pero no el id, o para comprobar un fichero que ya te
bajaste sin volver a descargarlo:

```bash
curl -sI https://minecraft.sinul.es/d/pack-del-server.zip \
| tr -d '\r' | awk -F': ' 'tolower($1)=="x-package-sha1"{print $2}'
```

### Buscar y filtrar

```bash
curl -s --get --data-urlencode 'q=texturas' https://minecraft.sinul.es/api/packages
curl -s 'https://minecraft.sinul.es/api/packages?category=plugin'
```

### Descargar

```bash
curl -sL -O https://minecraft.sinul.es/d/pack-del-server.zip
```

### Borrar

```bash
curl -X DELETE https://minecraft.sinul.es/api/packages/pack-del-server
```

---

## 5. La API entera

| Método | Ruta | Qué hace |
| --- | --- | --- |
| `GET` | `/api/health` | Estado del servicio. |
| `GET` | `/api/config` | URL pública, tamaño máximo y si hace falta token. |
| `GET` | `/api/categories` | Categorías disponibles. |
| `GET` | `/api/packages` | Catálogo. Acepta `?q=` y `?category=`. |
| `GET` | `/api/packages/:id` | Una entrada. |
| `POST` | `/api/packages` | Subida (`multipart`: `file`, `name`, `category`, `description`). Devuelve `201` si crea y `200` si reemplaza, con `replaced` en el JSON. |
| `PATCH` | `/api/packages/:id` | Cambia nombre, descripción o categoría. |
| `DELETE` | `/api/packages/:id` | Borra la entrada y su fichero. |
| `GET` | `/d/:fichero` | **Descarga directa** del binario. |

Los `GET` y las descargas son públicos. `POST`, `PATCH` y `DELETE` exigen la
cabecera `x-admin-token`; sin ella responden `401` y no tocan nada.

Cada entrada del catálogo se devuelve así:

```json
{
  "id": "pack-del-server",
  "name": "Pack del server",
  "description": "texturas de la temporada 3",
  "category": "resource-pack",
  "originalName": "pack.zip",
  "extension": ".zip",
  "size": 200556,
  "sha1": "6802cc4c72c48fae414cbdc932447829f284ed11",
  "createdAt": "2026-09-04T08:55:10.464Z",
  "updatedAt": "2026-09-04T08:55:10.464Z",
  "downloads": 12,
  "url": "/d/pack-del-server.zip",
  "absoluteUrl": "https://minecraft.sinul.es/d/pack-del-server.zip"
}
```

### Sobre las descargas (`/d/**`)

Está pensado para que un cliente de Minecraft se lo trague sin problemas:

- devuelve el binario **tal cual**, sin redirecciones ni HTML de por medio;
- acepta `Range`, o sea que una descarga cortada se puede **reanudar**;
- manda el hash en la cabecera `X-Package-Sha1`;
- la URL es corta a propósito, porque el campo `resource-pack` tiene límite de
  longitud en versiones antiguas de Minecraft;
- también responde sin la extensión: `/d/pack-del-server` vale igual.

---

## 6. Categorías

Vienen ocho de serie: `resource-pack`, `datapack`, `mod`, `modpack`, `plugin`,
`shader`, `mundo` y `otros`. Se pueden crear más desde el formulario con
`➕ Categoría nueva…`; el nombre se normaliza igual que las URLs
(`Mods de Terror!` → `mods-de-terror`).

Una categoría nueva aparece en los filtros en cuanto tiene algo dentro. Las ocho de
serie salen siempre, aunque estén vacías.

---

## 7. Límites y avisos

- **Tamaño máximo por fichero: 2 GB.** Si te pasas, sale un error claro y no se
  sube nada a medias.
- **Una subida grande tarda**, y depende de tu subida de internet, no del servidor.
  La barra de progreso es real: si avanza, va bien.
- **No cierres la pestaña** mientras sube.
- **Subir, editar y borrar exigen un token de administración.** La web lo pide una
  vez por navegador y lo recuerda; si te lo pide otra vez, es que se borraron los
  datos del sitio o el token cambió.
- **Listar y descargar son públicos**, sin token, porque es lo que necesitan los
  clientes de Minecraft.

---

## 8. Si algo falla

| Qué ves | Qué pasa |
| --- | --- |
| `El fichero supera el máximo de 2048 MB` | El fichero es demasiado grande. |
| `Token de administración incorrecto` (`401`) | Falta el token o no vale. En la web, recarga y vuelve a introducirlo. Por `curl`, añade `-H 'x-admin-token: …'`. |
| `502 Bad Gateway` | El servicio está caído. Ver el punto 9. |
| `404` al descargar | Ese paquete se borró, o la URL está mal escrita. |
| El pack no le llega a los jugadores | Comprueba que copiaste **las dos** líneas al `server.properties` y que **reiniciaste el servidor**. |
| Los jugadores lo descargan cada vez | Falta la línea `resource-pack-sha1`, o no coincide con el fichero. |

---

## 9. Operación (para quien administra la máquina)

El servicio corre con **pm2** en el puerto `4303`, detrás de nginx, con certificado
de Let's Encrypt que se renueva solo.

```bash
cd ~/Documents/myProjects/minecraft-packages-server

pm2 logs minecraft-packages-server     # ver qué está pasando
npm run pm2:restart                    # recompilar y reiniciar
pm2 restart minecraft-packages-server  # reiniciar sin recompilar
```

### Dónde vive todo

```
minecraft-packages-server/
├── data/
│   ├── catalog.json   ← los metadatos de todos los paquetes
│   ├── files/         ← los ficheros, nombrados <id><extensión>
│   └── tmp/           ← subidas a medias; se limpia solo cada hora
├── public/            ← la web y este manual
└── src/               ← el servidor
```

**Copia de seguridad**: basta con guardar la carpeta `data/`. Ahí está todo; el
resto es código.

### El token de administración

Está en `ADMIN_TOKEN`, dentro del `.env` (permisos `600`, fuera de git). Quién puede
hacer qué:

| | Sin token | Con token |
| --- | --- | --- |
| Ver el catálogo, buscar, filtrar | ✅ | ✅ |
| Descargar (`/d/**`) | ✅ | ✅ |
| Subir / reemplazar (`POST`) | ❌ `401` | ✅ |
| Editar datos (`PATCH`) | ❌ `401` | ✅ |
| Borrar (`DELETE`) | ❌ `401` | ✅ |

En la web se pide una sola vez y se guarda en ese navegador. Por `curl` va en la
cabecera `x-admin-token`:

```bash
curl -X POST https://minecraft.sinul.es/api/packages \
  -H 'x-admin-token: EL-TOKEN' \
  -F 'name=...' -F 'category=...' -F 'file=@pack.zip'
```

> Usa siempre `https://`. Con `http://` el servidor redirige, pero la cabecera ya
> habría viajado en claro en esa primera petición.

Para cambiarlo:

```bash
cd ~/Documents/myProjects/minecraft-packages-server
sed -i 's/^ADMIN_TOKEN=.*/ADMIN_TOKEN=el-nuevo/' .env
npm run pm2:restart
```

Al cambiarlo, los navegadores que tuvieran el viejo empezarán a dar `401`; se
vuelve a pedir solo. Dejar `ADMIN_TOKEN` vacío desactiva la protección y abre las
subidas a cualquiera.
