Compare commits
5 Commits
f2c2a821cd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dc5db63cd | |||
| 233c8fefbf | |||
| 3ca5f1b29f | |||
| 1e62f2a4d8 | |||
| 4b8d400923 |
+6
-1
@@ -5,6 +5,11 @@
|
||||
RADICALE_USERNAME=admin
|
||||
RADICALE_PASSWORD=tu_contraseña_segura_aqui
|
||||
|
||||
# Configuración de dominios públicos (Coolify)
|
||||
RADICALE_DOMAIN=radicale.tu-dominio.com
|
||||
INFCLOUD_DOMAIN=infcloud.tu-dominio.com
|
||||
COOLIFY_ENTRYPOINTS=https
|
||||
|
||||
# Configuración de Red
|
||||
RADICALE_PORT=5232
|
||||
INFCLOUD_PORT=8090
|
||||
@@ -14,4 +19,4 @@ TZ=Europe/Madrid
|
||||
|
||||
# URLs base (no cambiar a menos que sepas lo que haces)
|
||||
RADICALE_BASE_URL=http://localhost:5232
|
||||
INFCLOUD_BASE_URL=http://localhost:8090
|
||||
INFCLOUD_BASE_URL=http://localhost:8090
|
||||
|
||||
@@ -70,6 +70,11 @@ INFCLOUD_PORT=8090
|
||||
# Configuración de Zona Horaria
|
||||
TZ=Europe/Madrid
|
||||
|
||||
# Dominios públicos (Coolify)
|
||||
RADICALE_DOMAIN=radicale.tu-dominio.com
|
||||
INFCLOUD_DOMAIN=infcloud.tu-dominio.com
|
||||
COOLIFY_ENTRYPOINTS=https
|
||||
|
||||
# URLs base
|
||||
RADICALE_BASE_URL=http://localhost:5232
|
||||
INFCLOUD_BASE_URL=http://localhost:8090
|
||||
@@ -130,6 +135,16 @@ docker compose restart radicale
|
||||
- **Ruta principal:** /usuario/
|
||||
- **SSL:** No (para desarrollo local)
|
||||
|
||||
## 🌐 Integración con coolify-proxy
|
||||
|
||||
1. **Define los dominios públicos:** Rellena las variables `RADICALE_DOMAIN`, `INFCLOUD_DOMAIN` y `COOLIFY_ENTRYPOINTS` (por defecto `https`) en `.env`. Mantén `RADICALE_BASE_URL` e `INFCLOUD_BASE_URL` apuntando a las URLs HTTPS finales para que InfCloud genere enlaces correctos.
|
||||
2. **Genera la configuración de InfCloud:** `make config`.
|
||||
3. **Asegura la red externa:** Coolify crea una red llamada `coolify-proxy`. Si no existe ejecútalo una vez como root: `docker network create coolify-proxy`.
|
||||
4. **Ajusta y despliega los servicios:** `docker compose up -d` (o `make start`). Cada servicio se conectará automáticamente tanto a `caldav_net` como a `coolify-proxy` y expondrá las etiquetas Traefik necesarias.
|
||||
5. **Verifica la conexión a la red de Coolify:** `docker network inspect coolify-proxy | grep -E 'radicale|infcloud'`.
|
||||
6. **Crea los routers desde Coolify:** Dentro del panel de Coolify > Proxy, declara cada subdominio apuntando al contenedor correspondiente y al puerto interno (`5232` para Radicale, `80` para InfCloud). Traefik detectará los labels y emitirá los certificados usando el resolver indicado.
|
||||
7. **Pruebas rápidas:** `docker compose ps` para comprobar que ambos contenedores están arriba y `curl -u usuario:contraseña -X PROPFIND https://tu-subdominio-radicale/usuario/ -H "Depth: 0"` para validar la publicación detrás de Coolify.
|
||||
|
||||
## 🐛 Resolución de Problemas
|
||||
|
||||
### Error 405 PROPFIND
|
||||
@@ -224,4 +239,4 @@ make restart # Reiniciar servicios
|
||||
**Pruebas realizadas:**
|
||||
- ✅ admin: HTTP 207 Multi-Status
|
||||
- ✅ user1: HTTP 207 Multi-Status
|
||||
- ✅ Cualquier usuario nuevo funciona automáticamente
|
||||
- ✅ Cualquier usuario nuevo funciona automáticamente
|
||||
|
||||
@@ -14,6 +14,12 @@ services:
|
||||
- "${RADICALE_PORT}:5232"
|
||||
networks:
|
||||
- caldav_net
|
||||
- coolify-proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.radicale.rule=Host(`${RADICALE_DOMAIN}`)"
|
||||
- "traefik.http.routers.radicale.entrypoints=${COOLIFY_ENTRYPOINTS}"
|
||||
- "traefik.http.services.radicale.loadbalancer.server.port=5232"
|
||||
|
||||
infcloud:
|
||||
image: nginx:alpine
|
||||
@@ -30,7 +36,15 @@ services:
|
||||
- ./infcloud_config/nginx-simple.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- caldav_net
|
||||
- coolify-proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.infcloud.rule=Host(`${INFCLOUD_DOMAIN}`)"
|
||||
- "traefik.http.routers.infcloud.entrypoints=${COOLIFY_ENTRYPOINTS}"
|
||||
- "traefik.http.services.infcloud.loadbalancer.server.port=80"
|
||||
|
||||
networks:
|
||||
caldav_net:
|
||||
driver: bridge
|
||||
coolify-proxy:
|
||||
external: true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Configuración según documentación oficial de Radicale
|
||||
// https://github.com/Kozea/Radicale/wiki/Client-InfCloud
|
||||
var globalNetworkCheckSettings = {
|
||||
href: 'http://localhost:8090/radicale/',
|
||||
href: 'https://infcloud-h5j23io4jn45.organic-machine.com/radicale/',
|
||||
timeOut: 90000,
|
||||
lockTimeOut: 10000,
|
||||
checkContentType: false,
|
||||
@@ -28,7 +28,8 @@ var globalSearchTransformAlphabet = 'AÁÀÂàáâBCÇcçDEÉÈÊeéèêFGHIÍÌ
|
||||
// Configuración de calendario
|
||||
var globalCalendarSelected = '';
|
||||
var globalTodoCalendarSelected = '';
|
||||
var globalActiveView = 'multiWeek'; // Vista por defecto: semana múltiple
|
||||
var globalActiveView = 'agendaWeek'; // Vista por defecto: semana
|
||||
var globalDefaultEventDuration = 60; // Eventos nuevos duran 1 hora
|
||||
var globalOpenFormMode = 'double';
|
||||
var globalTodoListFilterSelected = ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'];
|
||||
var globalActiveApp = null;
|
||||
@@ -40,7 +41,7 @@ var globalDisplayHiddenEvents = false;
|
||||
|
||||
// Configuraciones adicionales de usuario que se recordarán
|
||||
var globalUserPreferences = {
|
||||
defaultView: 'multiWeek', // Vista preferida del usuario
|
||||
defaultView: 'agendaWeek', // Vista preferida del usuario
|
||||
showWeekends: true, // Mostrar fines de semana
|
||||
firstDayOfWeek: 1, // Lunes = 1, Domingo = 0
|
||||
workingHours: {start: 8, end: 18}, // Horario laboral
|
||||
@@ -95,7 +96,7 @@ var globalUseCrossOrigin = false;
|
||||
|
||||
// Variables adicionales requeridas
|
||||
var globalContactDataMinVisiblePercentage = 0.2;
|
||||
var globalEditorFadeAnimation = 666;
|
||||
var globalEditorFadeAnimation = 0;
|
||||
var globalEventStartPastLimit = 3;
|
||||
var globalEventStartFutureLimit = 3;
|
||||
var globalTodoStartPastLimit = 3;
|
||||
|
||||
@@ -28,7 +28,8 @@ var globalSearchTransformAlphabet = 'AÁÀÂàáâBCÇcçDEÉÈÊeéèêFGHIÍÌ
|
||||
// Configuración de calendario
|
||||
var globalCalendarSelected = '';
|
||||
var globalTodoCalendarSelected = '';
|
||||
var globalActiveView = 'multiWeek'; // Vista por defecto: semana múltiple
|
||||
var globalActiveView = 'agendaWeek'; // Vista por defecto: semana
|
||||
var globalDefaultEventDuration = 60; // Eventos nuevos duran 1 hora
|
||||
var globalOpenFormMode = 'double';
|
||||
var globalTodoListFilterSelected = ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'];
|
||||
var globalActiveApp = null;
|
||||
@@ -40,7 +41,7 @@ var globalDisplayHiddenEvents = false;
|
||||
|
||||
// Configuraciones adicionales de usuario que se recordarán
|
||||
var globalUserPreferences = {
|
||||
defaultView: 'multiWeek', // Vista preferida del usuario
|
||||
defaultView: 'agendaWeek', // Vista preferida del usuario
|
||||
showWeekends: true, // Mostrar fines de semana
|
||||
firstDayOfWeek: 1, // Lunes = 1, Domingo = 0
|
||||
workingHours: {start: 8, end: 18}, // Horario laboral
|
||||
@@ -95,7 +96,7 @@ var globalUseCrossOrigin = false;
|
||||
|
||||
// Variables adicionales requeridas
|
||||
var globalContactDataMinVisiblePercentage = 0.2;
|
||||
var globalEditorFadeAnimation = 666;
|
||||
var globalEditorFadeAnimation = 0;
|
||||
var globalEventStartPastLimit = 3;
|
||||
var globalEventStartFutureLimit = 3;
|
||||
var globalTodoStartPastLimit = 3;
|
||||
|
||||
@@ -298,10 +298,11 @@ body, input, select, textarea
|
||||
right: 583px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
display: none;
|
||||
z-index: 10;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
@@ -1202,10 +1203,11 @@ input.non_editable
|
||||
border-right: 1px solid;
|
||||
border-right-color: #c0c0c0;
|
||||
width: 224px;
|
||||
background: #ffffff;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
display: none;
|
||||
z-index: 10;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
@@ -1220,11 +1222,12 @@ input.non_editable
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
opacity: 0.8;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
opacity: 1;
|
||||
display: none;
|
||||
z-index: 22;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
@@ -2203,15 +2206,16 @@ body
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
overflow: visible;
|
||||
background: #ffffff;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
display: none;
|
||||
opacity: 0.8;
|
||||
opacity: 1;
|
||||
z-index: 99;
|
||||
cursor:default;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select: -moz-none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#CAEvent .saveLoader
|
||||
@@ -2613,9 +2617,8 @@ h3
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f9f9f9;
|
||||
opacity: 0.7;
|
||||
filter: alpha(opacity=70);
|
||||
background-color: rgba(249, 249, 249, 0.15);
|
||||
opacity: 1;
|
||||
z-index: 99;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
+70
-1
@@ -17,7 +17,76 @@ GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
|
||||
// Disable jQuery fade animations to make UI snappier
|
||||
(function (win) {
|
||||
if (!win || !win.jQuery) {
|
||||
return;
|
||||
}
|
||||
var $ = win.jQuery;
|
||||
if ($.fx && $.fx.off !== true) {
|
||||
$.fx.off = true;
|
||||
}
|
||||
if ($.fn._noAnimationPatch) {
|
||||
return;
|
||||
}
|
||||
function getCallback(args) {
|
||||
for (var i = args.length - 1; i >= 0; i--) {
|
||||
if (typeof args[i] === 'function') {
|
||||
return args[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function immediate(action) {
|
||||
return function () {
|
||||
var callback = getCallback(arguments);
|
||||
return this.each(function () {
|
||||
var el = $(this);
|
||||
if (action === 'show') {
|
||||
el.show().css('opacity', 1);
|
||||
} else if (action === 'hide') {
|
||||
el.hide().css('opacity', 0);
|
||||
}
|
||||
if (callback) {
|
||||
callback.call(this);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
$.fn.fadeIn = immediate('show');
|
||||
$.fn.fadeOut = immediate('hide');
|
||||
$.fn.fadeTo = function () {
|
||||
var callback = getCallback(arguments);
|
||||
var target = typeof arguments[1] === 'number' ? arguments[1] : 1;
|
||||
return this.each(function () {
|
||||
var el = $(this);
|
||||
el.css('opacity', target);
|
||||
if (target === 0) {
|
||||
el.hide();
|
||||
} else {
|
||||
el.show();
|
||||
}
|
||||
if (callback) {
|
||||
callback.call(this);
|
||||
}
|
||||
});
|
||||
};
|
||||
$.fn.fadeToggle = function () {
|
||||
var callback = getCallback(arguments);
|
||||
return this.each(function () {
|
||||
var el = $(this);
|
||||
var hidden = !el.is(':visible') || el.css('opacity') === '0';
|
||||
if (hidden) {
|
||||
el.fadeIn(callback);
|
||||
} else {
|
||||
el.fadeOut(callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
$.fn._noAnimationPatch = true;
|
||||
})(window);
|
||||
|
||||
var globalSettings={
|
||||
version: {value: (typeof globalSettingsVersion!='undefined' && globalSettingsVersion!=null) ? globalSettingsVersion : 1, locked:false},
|
||||
|
||||
@@ -76,6 +76,26 @@ prompt_password() {
|
||||
htpasswd_update() {
|
||||
local user="$1" password="$2"
|
||||
htpasswd -B -C "$DEFAULT_COST" -b "$USERS_FILE" "$user" "$password" >/dev/null
|
||||
normalize_bcrypt_prefix "$user"
|
||||
}
|
||||
|
||||
normalize_bcrypt_prefix() {
|
||||
local user="$1"
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "⚠️ python3 no disponible: no se pudo normalizar el hash bcrypt de '$user' (prefijo \$2y\$)." >&2
|
||||
echo " Radicale solo acepta hashes \$2b\$, por lo que este usuario podría fallar." >&2
|
||||
return
|
||||
fi
|
||||
python3 - "$USERS_FILE" "$user" <<'PY'
|
||||
import pathlib, re, sys
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
user = sys.argv[2]
|
||||
data = path.read_text()
|
||||
pattern = re.compile(rf'^({re.escape(user)}:\$)2y', re.M)
|
||||
updated, count = pattern.subn(r'\g<1>2b', data)
|
||||
if count:
|
||||
path.write_text(updated)
|
||||
PY
|
||||
}
|
||||
|
||||
list_users() {
|
||||
|
||||
Reference in New Issue
Block a user